FPGA学习手记(二) 简单功能仿真及Verilog基础电路设计
看了某兄的一篇文章,讲到学FPGA切不可急功近利,大概是受到STM32的C语言库快速入门的影响,在学习FPGA时,给自己制定了学习方针如是:掌握FPGA开发的一般流程 → 学习基本外设的设计方法 → NIOS II硬件系统的搭建→设计并DIY简单的项目 → ……
回首而顾,这不正是急功近利的典型例子?在经历了漫长的综合编译并成功在开发板上搭建NIOS II以后,发现自己并没有学会什么,自己不过是在将FPGA一步步转化成MCU来用。为了防止这弯路越走越深,开始学习基础的电路设计知识和Verilog用法,同时翻出了尘封已久的数电书(阿杜一定恨死朱红了,哈哈)。之后准备再了解一下Quartus下的仿真,以及Modelsim,如果没有用好仿真工具,也是会平添许多麻烦的。
把共性的东西用好以后再解决特性的问题,当然不能离开实际,在学的过程中动手操作。在余秋雨先生看来,读万卷书、行往里路就是要拿自己的理性与考察看待只是和脚下的路的关系,看时间与空间的关系,再反观书中谢谢是对的,那些没有写好。我很佩服这样的观点,现在做工程与搞理论的,如果埋头行路或是塞听读书,大概都是没有前途的吧。呵呵,又扯远了……
在学习电路之前,有必要先补充一些东西,即Quartus II的集成的一些非常实用的功能。
一、Netlist Viewers
还是以上一次的HelloFPGA为例,Quartus II除了提供HDL语言的开发环境,还提供了Netlist Viewers工具便于通过可视化界面直观的理解HDL语言在各层的表现形式(如寄存器传送级)。
Netlist Viewer工具包括RTL Viewer、Technology Map Viewer和State Machine Viewer三种,其位置在Task栏中,可分别通过双击相应的图标获取该视图。也可在工具栏的Tools→Netlist Viewers中找到。
其中RTL Viewer可生成寄存器传送级电路,双击RTL Viewer,可看到RTL视图如下:
由于这个模块的逻辑结构十分简单,只是将按键信号取反控制LED,基本不需要可视化图形的帮助就能理解,但是一些复杂的工程就需要这些工具的辅助。通过左侧的Hierarchy栏,可以将了解每一层的组成。
RTL只相当于一种中间形式的描述结构,而Technology Map Viewer描述了代码综合后的实际电路结构,由于本例的结构十分简单,所以和实际电路形式与RTL视图相同。下面是一个四路选择器的Technology Map视图,内部结构已经展开:
我们可以看到,组合逻辑电路都是靠逻辑门的组合实现的,而这就是PLD器件的基本原理:所有组合逻辑都可以靠逻辑门电路加以实现,而所有时序电路可以靠触发器和逻辑门电路实现。
State Machine Viewer用于状态机,以后用到了再说。了解Verilog代码对应的RTL和硬件结构对于FPGA使用者来说十分重要,毕竟FPGA不仅仅是在使用语言和编写代码,而是在设计实际的硬件。
二、简单功能仿真
仿真的作用不消我说,这里说一下不用TestBench的简单功能仿真。通过点击 新建一个波形文件,保存为HelloFPGA.vwf。
在左侧的空白处点击右键,选择Insert Node or Bus,插入要仿真的信号。
这里可以直接输入信号名称,我们选择Node Finder,使用查找工具。
Filter可以设置查找过滤,点击List显示可用的信号节点,通过下面的箭头选择要仿真的信号节点。
一路OK后,选中输入信号Key,这时候可以在右侧Key的时间轴中拖动选中一段时间,通过左侧工具栏进行置0、置1等操作。这里选择 将Key设置成时钟信号。
这里将信号终止时间设置为1us,将时钟周期设置为100ns。
Key信号就被设置成了一个周期100ns的时钟信号。
确认一下没有选择第三方的仿真工具,如下图。点 对工程进行编译,再点 进行仿真。
仿真结果如下,可以看到,LED信号对于Key信号会有延时存在延时。
通过Zoom选项放大缩小视图,在空白处右键选择Insert Time Bar插入时间条,将两个时间条拖到相应位置,可以看到延时为10ns左右。当然了,这个仿真工具只能在功能上验证波形的正确与否,更加复杂的仿真需要通过第三方工具来进行。
另外,仿真工具栏常用的功能如下,可以自己琢磨一下:
三、基础电路设计
这里所说的基础电路,是数电中的基本组合逻辑及时序电路。这次先开个头,说多了太勤快显得我名不副实……
1.全加器
这是一个全加器模块,a_in、b_in为加数,c_in为输入进位,sum为加和, c_out为输出进位。根据全加器的真值表,以及K图等方式,化简后可以得到逻辑关系(化简结果不唯一):
SUM=A⊕B⊕Cin
Cout=AB+BC+CA
数电忘光光的童鞋可以回去翻书了,呵呵,懒兔不再赘述。其真值表如下:
A |
B |
Cin |
Sum |
Cout |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
1 |
1 |
0 |
0 |
1 |
module FULLADD(a_in, b_in, c_in, sum, c_out);
input a_in,b_in,c_in;
output sum,c_out;
assign sum = a_in ^ b_in ^ c_in;
assign c_out = (a_in & b_in) | (b_in & c_in) | (a_in & c_in);
endmodule
可以看到,其RTL视图是通过与上述逻辑关系式相同的逻辑来表述代码的,但是这样的描述非常不直观,而且需要先行计算逻辑关系表达式。
我觉得,其实采用下面的方式,将逻辑关系式替换成三个1位二进制数直接相加,产生的进位和加和通过{ }符号连接起来,高位在前。
assign sum = a_in ^ b_in ^ c_in;
assign c_out = (a_in & b_in) | (b_in & c_in) | (a_in & c_in);
assign {c_out,sum}=a_in+b_in+c_in;
这样得到的RTL结果如下,代码被描述成两个加法器,然而经过综合编译后,其硬件组成和上面的表达方法是完全相同的,也就是说Quartus II自动进行了转换和化简。
下面是一个N位全加器的代码,其中N通过WIDTH定义,这里定义为8位。采用parameter和define的方式,可以增加代码的可读性和复用性,这和C是一样的。
module FULLADD(a_in, b_in, c_in, sum, c_out);
parameter WIDTH = 8;
input[WIDTH-1:0] a_in,b_in;
input c_in;
output[WIDTH-1:0] sum;
output c_out;
assign {c_out,sum}=a_in+b_in+c_in;
endmodule
RTL视图,只是数据线宽改变了:
仿真结果,结果正确性没的说,只是输出数据之间存在毛刺一样的波形,放大后发现是不正确的数据,一般我们认为这是竞争冒险导致的,然而jlx_cuc君则不这么认为,本着做学问的态度,有兴趣的朋友可以看看,传送门:由简单的加法器揭秘FPGA底层实现。
嗯嗯,先到这里吧,今天起的太早了,啊呜~~~
用户245457 2015-10-23 16:55