今天给大侠带来基于FPGA的电子计算器设计。话不多说,上货。
导读
本篇介绍了一个简单计算器的设计,基于 FPGA 硬件描述语言 Verilog HDL,系统设计由计算部分、显示部分和输入部分四个部分组成,计算以及存储主要用状态机来实现。显示部分由六个七段译码管组成,分别来显示输入数字,输入部分采用4*4矩阵键盘,由0-9一共十个数字按键,加减乘除四个运算符按键,一个等号按键组成的。通过外部的按键可以完成加、减、乘、除四种功能运算,其结构简单,易于实现。本篇为本人毕业设计部分整理,各位大侠可依据自己的需要进行阅读,参考学习。
前言
在国外,电子计算器在集成电路发明后,只用短短几年时间就完成了技术飞跃,经过激烈的市场竞争,现在的计算器技术己经相当成熟。计算器已慢慢地脱离原来的“辅助计算工具”的功能定位,正在向着多功能化、可编程化方向发展,在各个领域都得到了广泛的应用。用计算器不仅可以实现各种各样复杂的数学计算还可以用来编制、运行程序,甚至解方程组,图形计算器还可以进行图形处理。计算器内置的软件允许用户进行类似于对计算机的文件和目录管理等操作,允许用户对图形界面进行定制,同时各种新技术也被应用到计算器里使计算器功能越来越强大。可以说,计算器就是一个“微微型”的计算机。国内也有厂商利用计算器芯片开发新的产品,但对计算器技术的研究、计算器芯片的设计还处于起步阶段。计算器的主要功能还是在于“计算”,不妨称之为“低档计算器”。即便是对这种计算器,很多厂商也只从事计算器的组装、销售业务。一些IC设计公司、芯片提供商也开始研究计算器技术。
本次设计基于现场可编程逻辑器件FPGA进行设计,应用硬件描述语言Verilog编程并在Altera公司的QuartusⅡ软件上实现仿真。需要进行计算器的常用运算功能的实现,通过外接键盘输入、LED数码显示来达成运算目的。
一、绪论
Verilog HDL 是一种硬件描述语言(HDL:Hardware Description Language),以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式,还可以表示数字逻辑系统所完成的逻辑功能。Verilog HDL和VHDL是世界上最流行的两种硬件描述语言,都是在20世纪80年代中期开发出来的。前者由Gateway Design Automation公司(该公司于1989年被Cadence公司收购开发)。两种HDL均为IEEE标准。
1.1 Verilog HDL 的发展
Verilog是由Gateway设计自动化公司的工程师于1983年末创立的。当时Gateway设计自动化公司还叫做自动集成设计系统(Automated Integrated Design Systems),1985年公司将名字改成了前者。该公司的菲尔·莫比(Phil Moor by)完成了Verilog的主要设计工作。1990年,Gateway设计自动化被Cadence公司收购。
1990年代初,开放Verilog国际(Open Verilog International,OVI)组织(即现在的Accellera)成立,Verilog面向公有领域开放。1992年,该组织寻求将Verilog纳入电气电子工程师学会标准。最终,Verilog成为了电气电子工程师学会1364-1995标准,即通常所说的Verilog-95。
设计人员在使用这个版本的Verilog的过程中发现了一些可改进之处。为了解决用户在使用此版本Verilog过程中反映的问题,Verilog进行了修正和扩展,这部分内容后来再次被提交给电气电子工程师学会。这个扩展后的版本后来成为了电气电子工程学会1364-2001标准,即通常所说的Verilog-2001。Verilog-2001是对Verilog-95的一个重大改进版本,它具备一些新的实用功能,例如敏感列表、多维数组、生成语句块、命名端口连接等。目前,Verilog-2001是Verilog的最主流版本,被大多数商业电子设计自动化软件包支持。
2005年,Verilog再次进行了更新,即电气电子工程师学会1364-2005标准。该版本只是对上一版本的细微修正。这个版本还包括了一个相对独立的新部分,即Verilog-AMS。这个扩展使得传统的Verilog可以对集成的模拟和混合信号系统进行建模。容易与电气电子工程师学会1364-2005标准混淆的是加强硬件验证语言特性的SystemVerilog(电气电子工程师学会1800-2005标准),它是Verilog-2005的一个超集,它是硬件描述语言、硬件验证语言(针对验证的需求,特别加强了面向对象特性)的一个集成。
2009年,IEEE 1364-2005和IEEE 1800-2005一共两个部分合并为IEEE 1800-2009,成为了一个新的、统一的SystemVerilog硬件描述验证语言(hardware description and verification language,HDVL)。
1.2 Verilog HDL 的特点
描述复杂的硬件电路,设计人员总是将复杂的功能划分为简单的功能,模块是提供每个简单功能的基本结构。设计人员可以采取“自顶向下”的思路,将复杂的功能模块划分为低层次的模块。这一步通常是由系统级的总设计师完成,而低层次的模块则由下一级的设计人员完成。自顶向下的设计方式有利于系统级别层次划分和管理,并提高了效率、降低了成本。“自底向上”方式是“自顶向下”方式的逆过程。
1.3 Verilog HDL 的语言结构
Verilog的设计初衷是成为一种基本语法与C语言相近的硬件描述语言。这是因为C语言在Verilog设计之初,已经在许多领域得到广泛应用,C语言的许多语言要素已经被许多人习惯。一种与C语言相似的硬件描述语言,可以让电路设计人员更容易学习和接受。不过,Verilog与C语言还是存在许多差别。另外,作为一种与普通计算机编程语言不同的硬件描述语言,它还具有一些独特的语言要素,例如向量形式的线网和寄存器、过程中的非阻塞赋值等。总的来说,具备C语言的设计人员将能够很快掌握Verilog硬件描述语言。下面介绍Verilog语言基本规范:
1)空白符
空白符是指代码中的空格(对应的转义标识符为\b)、制表符(\t)和换行(\n)。如果这些空白符出现在字符串里,那么它们不可忽略。除此之外,代码中的其他空白符在编译的时候都将会被视为分隔标识符,即使用2个空格或者1个空格并无影响。不过,在代码中使用合适的空格,可以让上下行代码的外观一致(例如使赋值运算符位于同一个竖直列),从而提高代码的可读性。
2)注释
为了方便代码的修改或其他人的阅读,设计人员通常会在代码中加入注释。与C语言一样,有两种方式书写注释。第一种为多行注释,即注释从/*开始,直到*/才结束;另一种为单行注释,注释从//开始,从这里到这一行末尾的内容会被系统识别为注释。
3)某些电子设计自动化工具,会识别出代码中以特殊格式书写、含有某些预先约定关键词的注释,并从这些注释所提取有用的信息。这些注释不是供人阅读,而是向第三方工具提供有关设计项目的额外信息。例如,某些逻辑综合工具可以从注释中读取综合的约束信息。
4)大小写敏感性
Verilog是一种大小写敏感的硬件描述语言。其中,它的所有系统关键字都是小写的。
5)标识符及保留字
Verilog代码中用来定义语言结构名称的字符称为标识符,包括变量名、端口名、模块名等等。标识符可以由字母、数字、下划线以及美元符($)来表示。但是标识符的第一个字符只能是字母、数字或者下划线,不能为美元符,这是因为以美元符开始的标识符和系统任务的保留字冲突。
和其他许多编程语言类似,Verilog也有许多保留字(或称为关键字),用户定义的标识符不能够和保留字相同。Verilog的保留字均为小写。变量类型中的wire、reg、integer等、表示过程的initial、always等,以及所有其他的系统任务、编译指令,都是关键字。可以查阅官方文献以完整的关键字的列表。
1.4 FPGA开发环境简介
系统电路的软件设计可采用工具软件Quartus Ⅱ,用该工具软件所支持的语言——硬件描述语言,以文本的方式进行编程输入。在编程时分别对控制、计数、锁存、译码等电路模块进行Verilog文本描述,使每个电路模块以及器件都以文本的形式出现,然后通过编译、波形分析、仿真、调试来完善每个器件的功能。单个器件制作完成后,然后将它们生成库文件,并产生相应的符号,最后用语言将各个已生成库文件的器件的各个端口连接在一起,从而形成了系统主电路的软件结构。在连接器件时,也可以采用图形输入方式,即在图形输入界面中调出先制作好的库文件器件符号,再将每个器件符号的各端口直接连线,从而构成系统主电路。在上述工作的基础上,再进行波形分析、仿真调试便完成整个软件设计。
二、现场可编程门阵列(FPGA)简介
2.1 可编程逻辑器件简介
可编程逻辑器件(PLD——Programmable Logic Devices)是一种由用户编程以实现某种逻辑功能的新型逻辑器件。它诞生于20世纪70年代,在20世纪80年代以后,随着集成电路技术和计算机技术的发展而迅速发展起来的。可编程逻辑器件自问世以来,PLD经历了从PROM、PLA、PAL、GAL到FPGA、ispLSI等高密度PLD的发展过程。在此期间,PLD的集成度、速度不断提高,功能不断增强,结构趋于更合理,使用变得更灵活方便。PLD的出现打破了由中小型通用型集成电路和大规模专用集成电路垄断的局面。与中小规模通用型集成电路相比,用PLD实现数字系统,有研制周期短、先期投资少、无风险、修改逻辑设计方便、小批量生产成本低等优势。
随着可编程逻辑器件性能价格比的不断提高,EDA开发软件的不断完善,现代电子系统的设计将越来越多地使用可编程逻辑器件,特别是大规模可编程逻辑器件。如果说一个电子系统可以像积木堆积起来的话,那么现在构成许多电子系统仅仅需要3种标准的积木块——微处理器、存储器和可编程逻辑器件,甚至只需一块大规模可编程逻辑器件。PAL(Programmable Array Logic)器件是20世纪70年代末期出现的一种低密度、一次性可编程逻辑器件。GAL(Generic Array Logic)器件是继PAL器件之后,在20世纪80年代中期推出的一种低密度可编程逻辑器件。它在结构上采用了输出逻辑宏单元(OLMC——Output Logic Macro Cell)结构形式,在工艺上吸收EEPROM的浮栅技术,从而使GAL器件具有可擦除、可重新编程、数据可长期保存的结构特点。CPLD(Complex Programmable Logic Device)是万门以上的复杂可编程逻辑器件,采用CMOS EPROM、EEPROM、快闪存储器和SRAM等编程技术,从而构成高密度、高速度和低功耗的可编程逻辑器件。
2.2 现场可编程门阵列(FPGA)
FPGA是现场可编程门阵列(Field Programmable Gate Array)的简称。FPGA器件及其开发系统是开发大规模数字集成电路的新技术。它利用计算机辅助设计,绘制出实现用户逻辑的原理图、编辑布尔方程或用硬件描述语言等方式作为设计输入;然后经一系列转换程序、自动布局布线、模拟仿真的过程;最后生成配置FPGA器件的数据文件,对FPGA器件初始化。这样就实现了满足用户要求的专用集成电路,真正达到了用户自行设计、自行研制和自行生产集成电路的目的。
FPGA是一种半定制的集成电路,其特点是直接面向用户,具有极大的灵活性和通用性,开发效率高,硬件测试和实现快捷,工作可靠性好而且技术维护简单。
FPGA相对于CPLD而言,其结构特点在于FPGA是基于查找表look-up-table的。查找表(look-up-table)简称为LUT,LUT本质上是一个RAM。FPGA使用4输入的LUT,所以每一个LUT 可以看成一个有4位地址线的16*1的RAM。当用户通过原理图或HDL语言描述了一个逻辑电路后,PLD/FPGA开发软件会自动计算逻辑电路的所有可能的结果,并把结果事先写入RAM,这样,每输入一个信号进行逻辑运算就等于输入一个地址进行查表,找出地址对应的内容,然后输出即可。
2.2.1 FPGA的器件结构与工作原理
FPGA(Field Programmable Gate Array)即现场可编程逻辑阵列,是大规模可编程集成电路的主流器件。FPGA一般由三种可编程电路和一个用于存放编程数据的SRAM(静态随机存储器)组成,这三种可编程电路是:可编程逻辑阵列LAB(Logic Array Block),输入输出模块IOB(I/O Block)和互连资源IR(Interconnect Resource)。FPGA可编程逻辑形成的方法是基于查找表LUT(Look Up Table)结构的,LUT是可编程的最小逻辑构成单元。
1)可编程逻辑阵列LAB
可编程逻辑阵列是由一系列相邻的逻辑单元LE(Logic Element)构成的,每个LAB包括八个逻辑单元LE、相连的进位链和级联链,LAB控制信号和LAB局部互连。LAB的构成、ACEK系列芯片的“粗粒度(coarse-grained)”结构,有利于EDA软件进行布局布线,优化器件的利用进而提高整个数字系统的性。
其中的逻辑单元LE是一种基于查找表的函数发生器。它能够实现4输入1输出的任意逻辑函数。每个LE包含一个4输入的查找表、一个带有同步使能的可编程触发器、一个进位链和一个级联链。每个LE有两个输出分别可以驱动局部互连和快速通道互连。LE有两个输出驱动内部互连,一个是驱动局部互连输出,另一个驱动行或列的快速通道Fast Track的互连输出,这两个输出可以单独控制。因此在一个逻辑单元LE中的触发器和查找表能够用来完成不相关的功能,从而提高LE的资源利用率。
在ACEK系列芯片的结构中还提供了两种专用的高速数据通道,用于连接相邻的LE,但不占用局部互连通路,它们是进位链和级联链。进位链用来支持高速计数器和加法器,它提供了LE之间的快速向前进位功能。来自低位的进位信号经进位链向前直接送到高位,同时反馈入查找表和进位链的下一段。这种特点使得ACEK结构能够实现高速计数器、加法器和宽位比较器。级联链可以用来实现多输入数的逻辑函数。相邻的查找表并行地完成部分逻辑功能,级联链把中间结果拼接起来。进位链和级联链的使用有利于提高器件的工作速度,但是大量使用进位链和级联链会限制布局布线的灵活性,导致资源的浪费。因此在设计过程应该权衡考虑,在FPGA芯片资源利用和工作速度之间寻求平衡。
2)输入/输出模块IOB
ACEK器件的I/O引脚是由一些I/O单元驱动的。IOE(I/O Element)位于快速通道的行和列末端,包含一个双向的缓冲器和一个寄存器。这个寄存器可以用作需要快速建立时间的外部数据输入寄存器,也可以作为需要快速“时钟到输出”性能的数据输出寄存器。IOE可以配置成输入、输出或双向口。
ACEK器件中的IOE具有许多特性,支持JTAG编程、三态缓冲和漏极开路输出等等。每个IOE的时钟、清零、时钟使能和输出使能的控制均由I/O控制信号网络提供,采用高速驱动以减小通过器件的时间偏差。此外,ACEK器件还提供了若干专用输入引脚,这些引脚用来驱动IOE寄存器的控制端,使用了专用的布线通道,以便具有比快速通道更短的延迟和更小的偏差。
3)互连资源IR
可编程的互连资源包括各种长度的金属连线线段和一些可编程的连线开关,它们将各个逻辑阵列之间、及其与IO模块之间互相连接起来,构成各种功能复杂的系统。
在ACEK中互连结构是通过快速通道(Fast Track)实现的。Fast Track遍布于整个ACEK器件,是一系列水平和垂直走向的连续式布线通道。每一行的LAB都有一个专用的“行互连”,“行互连”可以驱动。I/O引脚或馈送到器件中的其LAB;“列互连”连接各行,也能驱动I/O引脚。这种布线结构能够有效提高布线效率,使得即使非常复杂的设计也能够测定其延时性。
4)嵌入式阵列块EAB
嵌入式阵列块EAB是在输入输出口上带有寄存器的灵活的RAM块,是由一系列嵌式RAM单元组成的。EAB的逻辑功能是在配置期间,用只读模式对EAB编程产生一个大型查找表来实现的。采用查找表实现组合逻辑比一般算法快,EAB的快速时间通道使得这一先进性能进一步得到加强。当要实现存储器功能时,每个EAB提供2048比特位,每一个EAB是一个独立的结构,它具有共同的输入、互连和控制信号。每个EAB含有一个行互连馈端,EAB的输出能够同时驱动行互连通道和列互连通道。这一特性增加了EAB的可利用布线资源。因此,EAB可以非常方便地用于实现一些规模不大的RAM、ROM、FIFO等功能模块;同时在实现地址译码器、状态机、微控制器等复杂逻辑时也具备了一定优势。
2.2.2 基于EDA平台的FPGA开发流程
一个完整、典型的EDA设计流程即是自顶向下设计方法的具体实施过程,也是EDA软件本身的组成结构。在实践中进一步了解支持这一个设计流程的诸多设计工具,有利于排解设计中的具体问题,提高设计质量。
1)设计输入
基于EDA软件平台的FPGA开发流程,一般有两种设计输入方式:图形输入和硬件描述语言文本输入。下面将重点介绍采用Verilog硬件描述语言进行设计输入方法,这是我们在设计开发过程中采用的主要方法。Verilog作为电子工程主流硬件描述语言,是EDA技术的重要组成部分。它于1983年由美国国防部创建,由IEEE协会进一步发展并在1987年成为IEEE国际标准。自IEEE协会公布了Verilog标准版本(IEEE Std 1076)之后,各大EDA公司相继推出支持Verilog语言的开发环境。从此Verilog作为硬件描述语言的业界标准之一,在电子设计领域得到广泛应用,并逐步成为事实上的通用硬件描述语言。
Verilog语言具有很强的电路建模能力,具有良好的电路行为描述和系统描述的能力,能从多个层次对数字系统进行建模和描述;Verilog语言还具有与硬件电路无关和设计平台无关的特性,并且在语言易读性和层次化结构化设计方面表现了强大的生命力和应用潜力。这些特性使得Verilog语言在支持自顶向下的EDA设计流程方面显得游刃有余。因此,采用Verilog进行电子系统设计可以让设计者专心致力于其功能的实现,而不需要对不影响功能的与工艺相关的问题花费过多的时间和精力,提高了设计效率和可靠性。
采用Verilog文本设计输入与传统的计算机软件语言编辑输入基本上一样,就是使用Verilog描述数字系统的功能,进行文本编辑输入。事实上,纯粹的Verilog设计输入方法仍然是最基本、最有效和最普遍的设计输入方法。
2)设计综合
在电子设计领域“综合”的概念可以表示为:将行为和功能层次表达的电子系统转化为低层次的便于具体实现的模块组合装配而成的过程。事实上,设计过程通常从高层次的行为描述开始,直到最底层的结构描述结束,每一个步骤都是上一个层次的综合转换。在FPGA设计开发过程中,整个综合过程就是设计者在EDA软件平台上编辑输入Verilog文本,依据给定电路结构组件和约束控制条件进行编译、优化、转换和综合,最终获得门级电路甚至更底层的电路描述网表文件。因此设计综合的过程就是将软件设计的Verilog描述文本与硬件结构挂钩,是软件描述转化为硬件电路的关键步骤,是文本描述与硬件实现之间的一座桥梁。
3)结构适配
适配器也称为结构综合器,它的功能是将综合器产生的网表文件配置于指定的FPGA目标器件中,使之产生最终的下载文件。适配过程中所选定的FPGA目标器件必须属于综合器原来指定的目标器件系列,这是因为适配器的适配对象是直接与器件的结构细节相对应的。逻辑综合通过后必须利用适配器将综合后的网表文件针对某一具体目标器件进行逻辑映射操作,其中包括底层器件配置、逻辑分割、逻辑优化、逻辑布局布线操作。适配完成后可以利用适配所产生的仿真文件作精确的时序仿真,同时产生可用于编程下载文件。
4)功能仿真和时序仿真
在编程下载前必须利用EDA工具对配置生成的结果进行模拟分析,这就是所谓的仿真。仿真的过程就是让计算机根据一定的算法和一定的仿真库对EDA设计进行模拟,以验证设计,排除错误。仿真包括功能仿真和时序仿真。
功能仿真:不经过综合和适配阶段,在设计项目编译后直接进入门级仿真器进行模拟测试。主要用于测试设计项目的逻辑功能,用以了解其实现的功能是否满足设计要求,在功能仿真的过程不涉及任何具体器件的硬件特性。
时序仿真:当设计项目通过功能仿真初步确定满足设计要求后,需要绑定具体器件进行时序仿真。时序仿真就是接近真实器件运行特性的仿真,仿真文件包含了器件硬件特性参数,因而仿真精度高。
5)编程下载
把适配后生成的下载或配置文件,通过编程器或编程电缆向FPGA进行下载,以便进行硬件调试和验证,从而实现满足设计要求的电子系统。
三、整体设计原理介绍
3.1 数码管显示
数码管的显示分为两种,静态显示和动态显示,在这里我们使用的是动态显示。动态显示的特点是将所有位数码管的段选并联在一起,由位选线控制是哪一位数码管是有效的。这样一来,就没有为每一位数码管配置一个锁存器的必要,从而就会大大简化了硬件电路。选亮的数码管采用动态扫描显示。所谓的动态扫描显示就是轮流向各位数码管送出字形码和相应的位选,利用发光管的余辉和人眼视觉暂留作用,使人的感觉好像各位数码管在同时都在显示。动态的显示的亮度要比静态的显示略差了一些,因而我们在选择需要的限流电阻应小于静态显示电路中的。
3.2 按键部分原理
我们采用了4*4矩阵键盘扫描在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式。在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接。这样,一个端口就可以构成4*4=16个按键,比之直接将端口线的应用于键盘多出了一倍,而且线数越多,区别越明显,比如再多加一条线就可以构成20键的键盘,而直接用端口线则只能多出一键(9键)。由此可见,我们在需要的键数比较多时,采用矩阵法来做键盘是合理的。
矩阵式结构的键盘显然比直接法要复杂一些,识别也要复杂一些,列线通过了电阻连接正电源,并将行线所接的I/O口作为输出端,而列线所接的I/O口则作为输入。这样,当按键没有按下时,所有的输入端都是高电平,代表无键按下。行线输出是低电平,一旦有键按下,则输入线就会被拉低,这样,通过读入输入线的状态就可得知是否有键按下了。下面我们介绍行扫描法。
行扫描法又称为逐行(或列)扫描查询法,是一种最常用的按键识别方法,介绍过程如下。
首先判断键盘中有无键按下:将全部行线置低电平,然后检测列线的状态。只要有一列的电平为低,则表示键盘中有键被按下,而且闭合的键位于低电平线与4根行线相交叉的4个按键之中。若所有的列线均是高电平,则键盘中无键按下。
其次判断闭合键所存在的位置:在确认有键按下后,即可进入确定具体闭合键的过程。其方法是:依次将行线置为低电平,就是在置某根行线为低电平时,其它线为高电平。在确定某根行线位置为低电平后,再逐行检测各列线的电平状态。若某列为低,则该列线与置为低电平的行线交叉处的按键就是闭合的按键。
运算部分我们主要应用了状态机进行运算和存储的,主要是有限状态机,下面对有限状态机进行简单的介绍。
状态机简写为FSM(Finite State Machine),主要分为2大类:第一类,若输出只和状态有关而与输入无关,则称为Moore状态机;第二类,输出不仅和状态有关而且和输入有关系,则称为Mealy状态机。要特别注意的是,因为Mealy状态机和输入有关,输出会受到输入的干扰,所以可能会产生毛刺(Glitch)现象,使用时应当注意。事实上现在市面上有很多EDA工具可以很方便的将状态图的描述转换成可以综合的Verilog程序代码。
关于状态机的一个极度确切的描述是它是一个有向图形,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每个事件都在属于“当前”节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少有一个必须是终态。当到达终态,状态机停止。
包含一组状态集(states)、一个起始状态(start state)、一组输入符号集(alphabet)、一个映射输入符号和当前状态到下一状态的转换函数(transition function)的计算模型。当输入符号串,模型随即进入起始状态。它要改变到新的状态,依赖于转换函数。在有限状态机中,会有有许多变量,例如,状态机有很多与动作(actions)转换(Mealy机)或状态(摩尔机)关联的动作,多重起始状态,基于没有输入符号的转换,或者指定符号和状态(非定有限状态机)的多个转换,指派给接收状态(识别者)的一个或多个状态,等等。
传统应用程序的控制流程基本是顺序的:遵循事先设定的逻辑,从头到尾地执行。很少有事件能改变标准执行流程;而且这些事件主要涉及异常情况。“命令行实用程序”是这种传统应用程序的典型例子。
另一类应用程序由外部发生的事件来驱动——换言之,事件在应用程序之外生成,无法由应用程序或程序员来控制。具体需要执行的代码取决于接收到的事件,或者它相对于其他事件的抵达时间。所以,控制流程既不能是顺序的,也不能是事先设定好的,因为它要依赖于外部事件。事件驱动的GUI应用程序是这种应用程序的典型例子,它们由命令和选择(也就是用户造成的事件)来驱动。
Web应用程序由提交的表单和用户请求的网页来驱动,它们也可划归到上述类别。但是,GUI应用程序对于接收到的事件仍有一定程度的控制,因为这些事件要依赖于向用户显示的窗口和控件,而窗口和控件是由程序员控制的。Web应用程序则不然,因为一旦用户采取不在预料之中的操作(比如使用浏览器的历史记录、手工输入链接以及模拟一次表单提交等等),就很容易打乱设计好的应用程序逻辑。
显然,必须采取不同的技术来处理这些情况。它能处理任何顺序的事件,并能提供有意义的响应,即使这些事件发生的顺序和预计的不同。有限状态机正是为了满足这方面的要求而设计的。
有限状态机是一种概念性机器,它能采取某种操作来响应一个外部事件。具体采取的操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。之所以能够做到这一点,是因为机器能跟踪一个内部状态,它会在收到事件后进行更新。为一个事件而响应的行动不仅取决于事件本身,还取决于机器的内部状态。另外,采取的行动还会决定并更新机器的状态。这样一来,任何逻辑都可建模成一系列事件/状态组合。
状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。
有限状态机用于描述电路模型的时序行为,所有的输入都可以看成是模型的激励,所有的输出可以看成是模型对激励的响应。CLK提供时间基准。
时序电路模型可以表示为:R=F(t),这里F(t)是模型行为的描述。当电路的输出仅仅与状态时间有关时候,所描述的模型为摩尔型状态机;当电路的输出不仅与时间有关,也与当前的输入信号有关时,称为米利型状态机。
四、计算器设计的电路部分
FPGA最小系统是可以使FPGA正常工作的最简单的系统。它的外围电路尽量最少,只包括FPGA必要的控制电路。一般我们所说的FPGA的最小系统主要包括:FPGA芯片,下载电路,外部时钟,复位电路和电源。
我们采用了低电平复位电路,电路图如下
JTAG最初是用来对芯片进行测试的,JTAG的基本原理是在器件内部定义一个TAP(Test Access Port;测试访问口)通过专用的JTAG测试工具对内部节点进行测试。JTAG测试允许多个器件通过JTAG接口串联在一起,形成一个JTAG链,能实现对各个器件分别测试。如今,JTAG接口还常用于实现ISP(In——System Programmer,在系统编程),对FLASH等器件进行编程。
JTAG编程方式是在线编程,传统生产流程中先对芯片进行预编程然后再装到板上,简化的流程为先固定器件到电路板上,再用JTAG编程,从而大大加快工程进度。JTAG接口可对DSP芯片内部的所有部件进行编程。
JTAG(Joint Test Action Group,联合测试行动小组)是一种国际标准测试协议,主要用于芯片内部测试及对系统进行仿真、调试,JTAG技术是一种嵌入式调试技术,它在芯片内部封装了专门的测试电路TAP(Test Access Port,测试访问口),通过专用的JTAG测试工具对内部节点进行测试。
如今大多数比较复杂的器件都支持JTAG协议,如ARM、DSP、FPGA器件等。标准的JTAG接口是4线:TMS、TCK、TDI、TDO,分别为测试模式选择、测试时钟、测试数据输入和测试数据输出。如今JTAG接口的连接有两种标准,即14针接口和20针接口。
支持边界扫描的逻辑元器件与测试相关的所有外部通信都采用串行通信方式,允许测试指令及相关的测试数据串行送给元器件,然后允许把测试指令的执行结果从元器件中串行读出。为了完成这样的功能,边界扫描技术包含了一个与元器件的每个引脚相接,包含在边界扫描寄存器单元中的寄存器链,这样元器件的边界信号可以用扫描测试原理进行控制和观察,这也是边界扫描的含义。
闪存存储器是一类非易失性存储器,即使在供电电源关闭后仍能保持片内信息。数字电路中经常需要使用大容量存储器,串行Flash存储速度快,体积小,功耗低,在FPGA中设计中发挥的作用也越来越大,广泛应用于实现系统及功能验证。FPGA的灵活性和串行Flash的体积小的特点相结合,具有灵活性强实用性强等特点。我们的Flash程序存储电路如图4-3所示。
我们应用的LED数码管段数为7段,就是7个发光二极管,任意一个阿拉伯数字0-9都是可以通过亮灭组合来实现的。LED数码管根据LED的接法不同分为共阴和共阳两类,共阴极数码管公共端接地,共阳极数码管公共端接电源,另一个非公共端的引脚留给用户的I/O直接控制了,在这里我们采用共阳极数码管。我们在这个应用中,把公共端连接到了FPGA的I/O引脚上,这就是我们数码管的片选信号。如果我们FPGA的I/O引脚输出低电平0,那么这个数码管就是可以显示数字的,如果我们这个I/O输出高电平1。那么即使不管数码管的段选端输出0还是1,都没有办法将这7个发光二极管的任意一个点亮,这也达到了关闭数码管显示的效果。一旦这样,这个数码管的公共端被当作数码管片选引脚使用了,虽然不是名副其实的“片选”,也是可以达到我们想要的效果。了解LED的这些特性,对编程是很重要的,不同类型的数码管,除了它们的硬件电路有差异外,方法也是不同的。
我们要实现一个计算器,首先需要有计算器的输入信号,通常是使用连接在FPGA的GPIO接口上的pushbutton作为输入信号。简易计算器的输入信号比较少,只需要数字和运算符号。我们可以将其各自使用一个按键来表示,然后都连接在FPGA的通用接口上。此时,我们便引入了矩阵键盘构造计算器的输入。
我们采用的是4*4矩阵键盘电路,矩阵键盘又称行列式键盘,它是用4条I/O线作为行线,4条I/O线作为列线组成的键盘。在行线和列线的每一个交叉点上,设置一个按键。这样键盘中的按键的个数就是4*4=16个。这种行列式键盘结构能够有效地提高单片机系统中I/O口的利用率。其中的ROW0,ROW1,ROW2,ROW3,以及COL0,COL1,COL2,COL3信号分别连接到了FPGA上,使用一种独特的方法,这8个信号能够表示出16个按键各自按下的状态。即这16个按键只占用了FPGA的8个管脚,而使用每个按键都连接到了FPGA的输入管脚需要占用16个管脚。
行列矩阵采用了扫描的形式,其8个连接到FPGA上的信号是4个输入4个输出的。我们一般使用的其他按键方法都是将按键产生的信号作为输入。在这里,ROW0-ROW3为FPGA的输入信号,COL0-COL3是FPGA的输出信号;电路上,ROW0-ROW3还要连接一个上拉电阻。
如果左上方第一个按键被按下,ROW0和COL0连接在一起;如果不按下,两个信号则没有连接。16个按键表示16种连接的关系,在没有按键按下的时候,输出的COL信号就悬空了,输入的4个ROW信号收到上拉电阻的影响都是高电平1。如果说连接COL3与ROW2的按键被按下,那么FPGA的输入信号ROL2就等于输出信号COL3的值,其他的ROW输入信号则全部为1。
我们在FPGA内部使输出的COL0信号为0而其他的COL是1,那么不管其他的列上对应的按键都怎么按下,都有输入的FPGA的ROW为全1;仅仅当第一列的4个按键中有一个按下时,对应的行值为0,其余3个的行值为1,这样的话第一列所对应的按键就唯一确定下来了。在这样的输出状态下(COL[3:0]=1110),4个输入只能确定出来第一列的4个按键。要是按下的不是第一行的4个按键,那么输入值ROW[3:0]为1111,表示第一列没有按键被按下。于是我们开始扫描第二列,就是令COL[3:0]输出1101,然后查看ROW上是否有值为0。如果此列上仍然没有扫描到,就继续扫描下一列。
对于一组数码管动态扫描显示需要由两组信号来控制:一组是输出口输出的字形代码,用来控制显示的字形,称为段码;另一组是位输出口输出的控制信号,用来选择第几位数码管的工作,称为位码。
由于各位数码管的段线并联,段码的输出对各位数码管来说都是相同的。因此,同一时刻如果各位数码管的位选线都处于选通状态的话,位数码管将显示相同的字符。若要各位数码管能够显示出与本位相应的字符,就必须采用扫描显示方式。即在某一时刻,只让某一位的选线处于导通状态,而其他各位的位选线处于关闭状态。同时,段线上输出相应位要显示字符的字形码。这样同一时刻,只有选通的那一位显示出字符,而其它各位则是熄灭的,如此循环下去,就可以使各位数码管显示出将要显示的字符。
虽然这些字符是在不同时刻出现的,而且同一时刻只有一位显示,其它各位熄灭,但是由于数码管具有余辉特性和人眼有视觉暂留现象,只要每位数码管显示间隔足够短,给人眼的视觉印象就会是连续稳定地显示。
我们使用的是LM1117,LM1117是一个低压差电压调节器系列。其压差在1.2V输出,负载电流为800mA时为1.2V。它与国家半导体的工业标准器件LM317有相同的管脚排列。LM1117有可调电压的版本,通过2个外部电阻可实现1.25~13.8V输出电压范围。另外还有5个固定电压输出(1.8V、2.5V、2.85V、3.3V和5V)的型号。
LM1117提供电流限制和热保护。电路包含1个齐纳调节的带隙参考电压以确保输出电压的精度在±1%以内。LM1117系列具有LLP、TO-263、SOT-223、TO-220和TO-252 D-PAK封装。输出端需要一个至少10µF的钽电容来改善瞬态响应和稳定性。
五、总体代码设计
系统总体设计框图如图5-1所示。此设计由计算部分、存储部分、显示部分和输入部分组成。
计算器输入部分的设计最主要的是按键译码电路的设计和实现。计算器的输入部分是由0—9十个数字按键、加减乘除四则运算的运算符按键、一个等号按键和一个清零按键组成的,设计所要做的是对按键信息进行译码,使其在计算器内部可以使用。这里使用的是4*4键盘矩阵作为输入。数字按键译码电路的主体部分Verilog语言描述如下。
设计的键盘矩阵输入模块如图:
图5-4 键盘矩阵驱动模块
Figure 5-4 matrix keyboard driver module
在输入键盘矩阵驱动模块中,我们有三个输入,四个输出,其中clk为主时钟输入,rst_n为复位信号,row为4*4键盘的行输入信号,输出col列信号,data为键入的数字(0-15),valid为数字的脉冲信号,clk_1k为给计算模块输出的时钟。
存储部分用状态机和寄存器来实现,我们输入的数字应用了移位拼接的原理,若第一个输入的是数字键,则保存下来,第二次输入还是输入的是数字键时,第一个数值左移变为十位,第二次的为个位,第三次若还是数字键,那么第一个数值将变为百位,第二个为十位,第三个为个位,以此类推,直到有符号键输入。
当进行第一次计算时,第一个数字存放在num1里面。按下运算符以后,第二个数字放在bin_data里面。当再按下运算符号或者等号时,第一次计算的结果将存放在ans里面,同时reg清零,等待下一个数字的输入。进行第二次运算时,将num1里面的结果与reg里面新输入的数字进行运算,再将运算结果存放在num1里面,直到最后按下等号按键的时候,显示最终的运算结果。
程序框图如下:
图5-5 状态1程序框图
Figure 5-5 state 1 program block diagram
图5-6 状态2程序框图
Figure 5-6 state 2 program block diagram
计算部分驱动模块如图5-7所示,共有4个输入部分,一个输出。其中clk为时钟,flag为数字脉冲信号,rst_n为复位,key_data为按键数据输入,bin_data为二进制中间数据输出。
图5-7 计算部分驱动模块
Figure 5-7 calculation part of the driver module
显示部分是系统的输出部分,用于显示按键值及计算结果,由于数字系统的数据运算都是二进制的,而输出表达式都是BCD码,为了满足BCD码的译码显示,最方便的方法就是利用译码程序在FPGA中实现。本文采用的是共阳极7段数码管,显示数字时需要将对应管脚置为低电平,输出时,从左到右,按从高到低位的顺序依次接g、f、e、d、c、b、a,小数点为h,为最高位。七段译码器的基本结构如图5-8所示。
图5-8 七段译码器的结构
Figure5-8 The structure of the seven-segment decoder
Verilog硬件位选信号描述性语言描述如下图5-9所示:
图5-9 数码管显示位选程序代码
Figure 5-9 digital tube display program code
Verilog硬件段选信号描述性语言描述如下图5-10所示:
图5-10 数码管显示段选程序代码
Figure 5-10 digital tube display program code segment
图5-11 数码管显示驱动模块
Figure 5-11 digital tube display driver module
图5-11为数码管显示驱动模块,一共三个输入,两个输出,其中clk为时钟,rst_n为复位,data_in数据输入,sel为位选信号,seg为段选信号。
六、仿真验证设计
在仿真设计时,用到了Mentor公司的Modelsim,这是一款硬件描述语言仿真软件,该款软件不单单能提供十分友好的仿真环境,而且它也是我们业界第一个也是仅此一个的单内核支持VHDL和Verilog语言混合仿真的软件。它采用直接优化的编译技术、Tcl/Tk技术、和单一内核仿真技术,从而达到令人编译仿真速度快的效果,而且编译代码和整个平台没有关系,这样就更容易保护IP核,它是FPGA/ASIC设计的首选仿真软件。
Modelsim有不同版本,例如:SE、PE、LE和OEM,其中最高级的版本是SE,而集成在 Actel、Atmel以及Lattice等FPGA厂商设计工具中的都是其OEM版本。
Modelsim SE支持PC、UNIX和LINUX的混合平台;能给出十分全面到位以及高性能的验证功能;全面支持业界设定的广泛标准;同时Mentor Graphics公司提供了整个行业最出色的技术支持与服务。
Modelsim的主要特点有:
1)支持单内核的VHDL和Verilog混合在一起进行仿真处理;
2)具有源代码模版、助手以及项目管理功能;
3)汇聚了性能考核、波形参考、代码覆盖、数据流Chase X、Signal Spy、虚拟对象Virtual Object、Assertion窗口、Memory窗口、源码窗口显示信号值、信号条件断点等众多调试功能;
4)C和Tcl/Tk接口,C调试;
5)能够实现对System C的直接支持功能,同时可以和HDL任意混合使用;
6)能够实现System Verilog的设计功能;
7)可以做到对系统级描述语言进行最全面的支持;
8)可以单独或同时进行行为(behavioral)、RTL级、和门级(gate-level)的代码。
9)能够实现RTL和门级优化,编译仿真速率非常快,跨平台跨版本的仿真。
FPGA设计流程包括设计输入,仿真,综合,生成,板级验证等很多阶段。在整个设计流程中,完成设计输入并成功进行编译仅仅能说明设计符合一定的语法规范,并不能说明设计功能的正确性,这时,我们就需要通过仿真对设计进行验证。我们主要进行的是功能仿真,又叫逻辑仿真,是指在不考虑器件延时和布线延时的理想情况下对源代码进行逻辑功能验证;而时序仿真是在布局布线后进行。我们仿真是为了保证设计的正确性。
矩阵键盘测试程序流程框图如下:
图6-1 按键测试程序流程图
Figure 6-1 key testing program flow chart
首先,我们要先进行按键检测,判断是否有按键闭合,如果没有说明没有按键键入,那么返回就是我们常说的消抖,重新进行按键检测,直到有按键闭合。接下来会有10ms的延迟,保存键值;再继续判断按键是否松开,如果是则又会产生10ms的延迟,否则返回判断直到按键有松开为止,最后返回键值。
我们需要编译一个模拟键盘定义data0-15,然后模拟输入给FPGA一个行信号,FPGA接收行信号,同时输出给模拟键盘一个列信号,如果输出的列信号不存在低电平,那么行信号为4‘b1111,代表输入的按键不在本列上,继续扫描下一列直到找到相应的行信号为止。部分代码如图所示。
图6-2 键盘扫描部分代码
Figure 6-2 keyboard scan code
图6-3为键盘扫描仿真图,当我们按下1时,数据显示1,按下10显示10,按下2显示的是2,按下15显示15,仿真结果有效,程序编译正确。
图6-3 键盘扫描仿真
Figure 6-3 keyboard scanning simulation
加法计算举例,首先pnumber输入1,data_in输入也为1,扫描结果为1;然后输入10;pnumber输入为2,data_in输入为2,扫描结果为2;最后按键“=”,显示结果即为3。仿真显示结果正确,说明我们的编译代码没有问题,计算有效,计算器结果可信。
七、结论
本次电子计算器的设计是基于FPGA设计的,计算器基本上可以实现的加减乘除的功能。系统的计算部分、存储部分、显示部分和输入部分四个部分都可以完成设计要求,输入部分采用键盘矩阵原理,存储部分用状态机来实现,并进行了仿真。实现了防消抖的要求,计算结果较精确。达到了预期的要求目标。
附录:设计主体源代码
二进制转BCD代码:
module bin2bcd_12bit(bin, bcd);
input [19:0] bin;
output reg [23:0] bcd;
always @ (*)
begin
bcd[3:0] = bin%10;
bcd[7:4] = bin/10%10;
bcd[11:8] = bin/100%10;
bcd [15:12] = bin/1000%10;
bcd[19:16] = bin/10000%10;
bcd[23:20] = bin/100000%10;
end
endmodule
计算模块代码:
module calculator (clk, rst_n, flag, key_data, bin_data);
input clk;
input rst_n;
input flag;
input [3:0] key_data;
output reg [19:0] bin_data;
reg [1:0] state;
reg [19:0] num1;
reg [3:0] opcode;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
state <= 0;
num1 <= 0;
bin_data <= 0;
opcode <= 0;
end
else
begin
case (state)
0 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= bin_data * 10 + key_data;
end
else
begin
if (key_data == 14)
begin
state <= 0;
end
else
begin
opcode <= key_data;
state <= 1;
num1 <= bin_data;
bin_data <= 0;
end
end
end
else
begin
state <= 0;
end
end
1 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= bin_data * 10 + key_data;
end
else
begin
if (key_data == 14)
begin
case (opcode)
10 : begin bin_data <= num1 + bin_data; state <= 2; end
11 : begin bin_data <= num1 - bin_data; state <= 2; end
12 : begin bin_data <= num1 * bin_data; state <= 2; end
13 : begin bin_data <= num1 / bin_data; state <= 2; end
default : bin_data <= 0;
endcase
end
else
begin
state <= 1;
end
end
end
else
begin
state <= 1;
end
end
2 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= {16'd0,key_data};
state <= 0;
end
else
begin
if (key_data == 14)
begin
state <= 2;
end
else
begin
num1 <= bin_data;
opcode <= key_data;
bin_data <= 0;
state <=1;
end
end
end
else
begin
state <= 2;
end
end
endcase
end
end
endmodule
输入部分代码:
module key_board (clk, rst_n, row, col, data, valid, clk_1k);
input clk;
input rst_n;
input [3:0] row;
output reg [3:0] col;
output reg [3:0] data;
output reg valid;
output reg clk_1k;
reg [14:0] cnt;
parameter T1ms = 24999;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
clk_1k <= 1'b1;
cnt <= 15'd0;
end
else
begin
if (cnt < T1ms)
begin
cnt <= cnt + 15'd1;
end
else
begin
cnt <= 15'd0;
clk_1k <= ~clk_1k;
end
end
end
reg [7:0] row_col;
reg [1:0] state;
reg [4:0] count;
always @ (posedge clk_1k or negedge rst_n)
begin
if (!rst_n)
begin
col <= 4'b0000;
row_col <= 8'd0;
state <= 0;
valid <= 0;
count <= 0;
end
else
begin
case (state)
0 : begin
if (row == 4'b1111)
begin
col <= 4'b0000;
end
else
begin
state <= 1;
end
end
1 : begin
if (row == 4'b1111)
begin
state <= 0;
count <= 0;
end
else
begin
if (count < 19)
begin
count <= count + 1;
end
else
begin
count <= 0;
state <= 2;
col <= 4'b0111;
end
end
end
2 : begin
if (row == 4'b1111)
begin
col <= {col[2:0],col[3]};
state <= 2;
end
else
begin
row_col <= {row,col};
state <= 3;
valid <= 1;
end
end
3 : begin
if (row == 4'b1111)
begin
state <= 0;
valid <= 0;
end
else
begin
valid <= 0;
state <= 3;
end
end
default : state <= 0;
endcase
end
end
always @ (*)
begin
case (row_col)
8'b0111_0111 : data = 4'hf;
8'b0111_1011 : data = 4'he;
8'b0111_1101 : data = 4'hd;
8'b0111_1110 : data = 4'hc;
8'b1011_0111 : data = 4'hb;
8'b1011_1011 : data = 4'ha;
8'b1011_1101 : data = 4'h9;
8'b1011_1110 : data = 4'h8;
8'b1101_0111 : data = 4'h7;
8'b1101_1011 : data = 4'h6;
8'b1101_1101 : data = 4'h5;
8'b1101_1110 : data = 4'h4;
8'b1110_0111 : data = 4'h3;
8'b1110_1011 : data = 4'h2;
8'b1110_1101 : data = 4'h1;
8'b1110_1110 : data = 4'h0;
default : data = 4'h0;
endcase
end
endmodule
数码管顶层代码:
module seven_seg (clk, rst_n, data_in, sel, seg);
input clk;
input rst_n;
input [23:0] data_in;
output [2:0] sel;
output [7:0] seg;
wire clk_1k;
freq freq_dut(
.clk(clk),
.rst_n(rst_n),
.clk_1k(clk_1k)
);
sel_seg_encode sel_seg_encode_dut(
.clk(clk_1k),
.rst_n(rst_n),
.data_in(data_in),
.sel(sel),
.seg(seg)
);
endmodule
分频部分代码:
module freq (clk, rst_n, clk_1k);
input clk;
input rst_n;
output reg clk_1k;
reg [14:0] cnt;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
clk_1k <= 1;
cnt <= 0;
end
else
begin
if (cnt < 24_999)
begin
cnt <= cnt + 1;
end
else
begin
cnt <= 0;
clk_1k <= ~clk_1k;
end
end
end
endmodule
位选段选连接代码:
module sel_seg_encode (clk, rst_n, data_in, sel, seg);
input clk;
input rst_n;
input [23:0] data_in;
output [2:0] sel;
output [7:0] seg;
wire [3:0] num;
seg_encode seg_encode_dut(
.rst_n(rst_n),
.num(num),
.seg(seg)
);
sel_encode sel_encode_dut(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.num(num),
.sel(sel)
);
endmodule
段选部分代码:
module seg_encode (rst_n, num, seg);
input rst_n;
input [3:0] num;
output reg [7:0] seg;
always @ (*)
begin
if (!rst_n)
begin
seg = 8'b0000_0000;
end
else
begin
case (num)
0 : seg = 8'b1100_0000;
1 : seg = 8'b1111_1001;
2 : seg = 8'b1010_0100;
3 : seg = 8'b1011_0000;
4 : seg = 8'b1001_1001;
5 : seg = 8'b1001_0010;
6 : seg = 8'b1000_0010;
7 : seg = 8'b1111_1000;
8 : seg = 8'b1000_0000;
9 : seg = 8'b1001_0000;
default : seg = 8'b0000_0000;
endcase
end
end
endmodule
位选部分代码:
module sel_encode (clk, rst_n, data_in, num, sel);
input clk;
input rst_n;
input [23:0] data_in;
output reg [3:0] num;
output reg [2:0] sel;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
sel <= 0;
end
else
begin
if (sel < 5)
begin
sel <= sel + 1;
end
else
begin
sel <= 0;
end
end
end
always @ (*)
begin
if (!rst_n)
begin
num = 0;
end
else
begin
case (sel)
0 : num = data_in[23:20];
1 : num = data_in[19:16];
2 : num = data_in[15:12];
3 : num = data_in[11:8];
4 : num = data_in[7:4];
5 : num = data_in[3:0];
default : num = 0;
endcase
end
end
endmodule
本篇到此结束,各位大侠,有缘再见!
- -THE END- -
往期精选
FPGA技术江湖广发江湖帖
无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。
FPGA技术江湖微信交流群
加群主微信,备注姓名+公司/学校+岗位/专业进群
FPGA技术江湖QQ交流群
备注姓名+公司/学校+岗位/专业进群