[FPGA 学习记录] 层次化设计

层次化设计

在前面几小节当中,我们已经对简单组合逻辑中的多路选择器、3-8译码器以及半加器,它们的理论知识做了一个讲解,而且通过实验设计并实现了它们的功能,在我们的征途系列开发板上也得到了功能的验证。
在本小节当中,我们将会为大家讲解一种设计思想,就是我们的层次化设计思想。
在我们基础篇当中涉及的实验例程,它们的功能都是比较简单的,模块划分也是相对较少的,有些工程只需要一个模块就可以实现它的功能,比如说我们前面提到的多路选择器、3-8译码器和半加器,它们并不能很明显的体现我们的层次化设计思想。但是我们想让大家尽早的知道这种思想方法,以便在长期的学习过程中能够慢慢地体会。本章节我们就通过全加器的例子来为大家讲解一下层次化设计的思想。
本小节的主要内容可以分为两个部分:第一部分就是我们的理论学习部分,在这一部分我们会对层次化设计这种思想方法的理论知识做一个全面的讲解;第二部分是我们的实战演练,在实战演练部分我们会使用我们前面设计并实现的半加器模块,结合我们的层次化设计思想,生成一个具有全加器功能的电路,让大家对层次化设计的思想有一个更加深刻的理解。


下面就是第一部分:理论学习

1 理论学习

1.1 层次化设计

在我们的数字电路之中,根据模块层次不同,有两种基本的结构设计方法:一种是自底向上的设计方法、另一种就是自顶向下的设计方法。这两种方法都属于层次化设计思想,它们能够有助于我们对整个项目的系统和结构,有一个宏观的把控,这也是我们在每个基础章节,为什么进行模块绘制这么一个原因。虽然简单的系统不需要划分结构,但是我们如果养成这个好习惯,在后面的一些大型、多模块设计中是对我们有所帮助的。
首先先来看一下自底向上的设计方法

1.2 自底向上(Bottom-Up)

自底向上的设计是一种传统的设计方法。它对设计进行逐次划分的过程,是从存在的基本单元出发的,这个基本单元要么是已经构造出的单元,要么是其他项目开发好的单元,或者说我们可以通过购买得到的一些单元。由这些基本单元构建我们的高层单元,依次向上,直到构建我们的系统。在自底向上的建模方法中,我们首先对现有的功能块进行分析,这些功能块有可能是我们已经实现过的,有可能是其他项目已经实现的功能模块,也有可能是通过购买可以得到的单元,比如说像 IP 核,然后使用这些已经实现的模块,来搭建规模大一些的功能块。就像图 1.2.1 所示,得到我们的功能块,如此向上继续,就可以得到我们的系统模块

image-20231024114950837

图 1.2.1 自底向上的设计方法

如果对这种设计方法进行一个比喻,我们可以理解为是树叶通过树枝向树干靠拢的这么一个过程。

1.3 自上而下(Top-Down)

第二种设计方法是自上而下的设计方法,它是从系统级开始,然后把系统分为基本单元,然后把每个单元又划分为下一层次的基本单元,一直这样下去,直到可以使用 EDA 元件库中的元件来实现为止。

在这种设计方法中,我们首先,先定义的是顶层功能模块,进而分析,需要哪些构成顶层模块的必要的子模块,然后就往下划分,然后进一步对各个子模块进行分解,直到,到达无法进行分解的底层功能模块,然后我们实现这些底层功能模块的功能。如图 1.3.1 所示

image-20231024193540407

图 1.3.1 自上而下的设计方法

这个可以理解为:由树干,然后得到树枝,通过树枝进而得到我们的树叶,可以把它比喻为这种设计思想。

1.4 混合使用

在典型的设计方法中,两种方法是混合使用的。

我们的设计人员,首先是根据电路的体系结构定义顶层模块;逻辑设计者,确定如何根据功能将整个设计划分为子模块。与此同时,电路设计者对底层功能模块电路进行一个优化的设计,并进一步使用这些底层模块来搭建其高层模块。那么两者的工作,按照相反的方向独立进行,直到在中间点进行一个汇合。这个时候电路设计者已经创建了一个底层功能库,这里边具有独立完整的功能块或者是 IP 核,或者是一些逻辑门,而逻辑设计者,也通过使用自顶向下的一个方法将整个设计分解为由库单元构成的一个结构描述。如图 1.4.1 所示

image-20231024194813411

图 1.4.1 混合使用的设计方法

那么以上部分就是层次化设计思想的理论部分的学习,那么理论可能是比较难理解,或许大家对 FPGA 的设计有了一个比较深刻的理解之后,再回过头来看这些理论知识,就会有新的感悟。

那么为了加深大家对层次化设计的概念理解,我们开始进行实战演练。

2 实战演练

在实战演练部分,我们将会以全加器的实验工程为例,讲解一个简单的层次化的设计。

2.1 实验目标

我们的实验目标,是使用上一章节我们实现的半加器,结合我们层次化的设计思想,设计并实现一个全加器。

2.2 硬件资源

我们使用的硬件资源与半加器也是相同的,使用开发板上的按键和 LED 灯,进行全加器的验证。我们使用按键 KEY1、KEY2 和 KEY3 它们分别作为被加数 in_1、被加数 in_2 和我们的进位信号 cin,然后以 LED 灯 D6 和 D7 分别作为结果的输出 sum 和进位的输出 cout

image-20231024195527091

那么了解了实验目标和硬件资源,我们就开始程序的设计

2.3 程序设计

首先是要新建我们的文件体系,那么这个大家就应该是轻车熟路了。新建一个总的文件夹

20231024195751_HFzgMeaU1T

然后在 doc 文件夹,新建我们的 Visio 文件,打开我们的 Visio 文件

n3BOsvk8Dc

2.3.1 模块框图

那么波形工具箱添加完成之后,开始模块框图的绘制。首先在基本形状中选择圆角矩形,作为框图的主体,然后为顶层模块取一个名字

20231024200713_iSXh4HcOoo

在前面我们已经进行了半加器框图的绘制。半加器框图它是有两路输入信号,就是两个加数,然后有两路输出信号,一路是进位信号,一路是我们的加数和。全加器与半加器唯一的不同就是:输入除了有两个加数之外,还有一个加数,这个加数就是上一级加法器的进位信号。这样就相当于它有三路输入,然后输出还是一个进位、一个结果位。全加器的功能就相当于是三个 1bit 的加数相加求和。
下面就开始输入、输出信号的绘制

20231024201258_oN9wIr9Q0G

那么到了这里,模块框图已经绘制完成。

在之前的设计当中,分析到这里就已经结束了我们就可以开始波形图的绘制,然后根据我们的波形图进行代码的编写,进而实现三个 1bit 数加和的功能。
但是这里,我们不想使用这种简单的方法,我们想要使用层次化的结构方法,将顶层模块进行进一步的划分。

2.3.1.1 模块划分

之前我们之所以没有采用这种层次化的设计方法,是因为:之前的实验工程,它实现的功能相对简单,一个模块就能够很好的实现完整的功能,而且顶层模块也不容易往下划分为更加独立的小模块了,直接根据功能写代码就可以了。然而,在全加器这个实验工程中,顶层模块却有所不同,因为我们在学习数电的时候都知道,我们的全加器并不是最基本的结构,它可以由两个半加器构成。也就是说我们可以根据之前设计的半加器,通过一定的组合,再加上适当的门电路来构成一个全加器。我们先绘制出半加器的框图

20231024201727_FIDeZdrRHM

半加器构成全加器的推导方式有很多种,我们这里使用其中一个方法。

我们都知道全加器有三个 1bit 的加数。我们可以先实现两个数的相加,再加上第三个数,这样不会影响最后的结果。而且两个数的加和,就可以使用我们的半加器来进行实现,然后输出求和信号和一个进位信号。第一个半加器输出的求和信号再和第三个加数相加,需要使用第二个半加器,然后会输出一个进位信号和最后的总的求和信号。

生成的进位信号会有两个,那这两个进位信号都是有用的,但是它们又不会同时存在,只要一个有效就可以。

所以说,将两个半加器的进位信号用一个或门运算后,作为最后的进位信号输出。那么接下来,我们就使用半加器来构建我们的全加器

20231024202320_RLTujya5xq

为了将两个半加器进行区分,第一个半加器它的后缀名我们加一个 1,然后将输入到全加器的前两位加数,分别输入到第一个半加器的输入信号 1 和输入信号 2 当中。两位加数输入到第一个半加器当中,就会输出进位信号和数据求和信号,然后我们可以使用第二个半加器,第二个半加器添加一个后缀 2。为了区分这两个半加器,还可以填充不同的颜色

20231024202658_KlnVLsWSbW

我们将第一个半加器输出的求和信号作为输入信号,输入到第二个半加器作为它的加数。这里为了对两个半加器输出的求和信号 sum 进行区分,我们使用一个新的 wire 型变量 half1_sum,作为中间变量来连接第一个半加器输出的 sum 信号和第二个半加器的输入信号 1

20231024203341_q6DwaDZXLx

然后将输入到全加器的进位信号与第二个半加器的输入信号 2 相连接

20231024203526_pDNxnGMgMo

第二个半加器会输出最终的求和信号和另一个进位信号

20231024203650_YWExFcRINC

我们前面已经提到了:产生的两个进位信号都是有用的,但是它们又不会同时存在,一个有效即有效,所以说将两个半加器的进位信号可以使用一个或门运算后,作为最后的输出进位信号。所以说这儿需要添加一个逻辑或门

20231024204345_T8Hl80mWn3

为了对两个半加器的进位信号进行区分,我们这儿可以定义两个变量 half1_cout、half2_cout。两个半加器产生的进位信号经过一个或运算,得到我们最终输出的一个进位信号

20231024205013_lnwtguZK71

这样我们就得到了由两个半加器来实现我们全加器功能的这个整体框图。

2.4 代码编写

因为前面我们已经完成了半加器功能代码的编写,如果使用半加器来构成我们的全加器,我们只需要在全加器的顶层文件中实例化我们的半加器,调用它的功能就可以了。而我们两个半加器的进位信号,只需要一个或语句就可以了。接下来我们直接进行代码的编写
首先我们要找到我们的半加器,然后复制半加器的功能模块,把半加器的 .v 文件,复制到全加器的目录下。然后需要新建一个全加器的顶层文件,然后同时打开两个 .v 文件

20231024205849_653Avj5a8J

我们开始顶层文件的编写。

全加器的输入信号有三路:两路被加数、一路进位信号。它的输出信号有两路:一个是求和信号、一个是进位信号。我们的求和信号是由第二个半加器直接输出,所以说使用 wire 型;我们的进位信号是由两个半加器的进位信号进行或运算得到的,我们这里打算使用 assign 语句,所以说进位信号也是 wire 型

20231024210316_oFDYVaGrMH

端口列表编写完成。
既然是使用我们的半加器实现全加器,那么一定要对半加器进行实例化

20231024221447_ZTVyCIqCVA

全加器当中对半加器进行了两次实例化,那么实例化名称一定不能相同。我们这里使用 1 后缀代表第一个半加器,使用 2 后缀代表第二个半加器。

将输入到全加器的两个被加数 in_1 和 in_2 作为输入信号输入到第一个半加器当中,两路输入信号输入到第一个半加器当中,第一个半加器就会输出两路信号:分别是求和信号和进位信号

20231024221701_lyoCiRK73q

为了将这两路信号引出来,我们声明两个 wire 型变量 half1_sum、half1_cout

20231024221836_jzAtoCYvW5

这里对变量进行了重新命名,就是为了区分信号来自于哪个半加器。

我们将输入到全加器的进位信号和第一个半加器输出的求和信号,作为输入信号输入到第二个半加器。第二个半加器也会输出两路信号,分别是进位信号和求和信号

20231024222128_PdWLW16ciT

第二个半加器产生的求和信号,就是我们最终要输出的求和信号,我们直接连接到输出端口

20231024222223_VR6QA7d5mR

两个半加器的进位信号,需要进行或运算之后,才能作为最终的进位信号进行输出。
首先我们声明一个 wire 型变量 half2_cout,将第二个半加器的进位信号引出来

20231024222934_za4Yhrsthl

到了这里,模块的实例化已经完成。
模块的实例化,不像是软件当中对于一个函数的调用,它最后会在 FPGA 芯片当中,产生两个半加器功能的硬件电路。这就好比我们使用两个半加器芯片来实现一个全加器。
接下来使用 assign 语句对两个进位信号进行或运算,把最终的结果作为进位信号的输出

20231024223239_6kCpwH7ISH

这样就完成了求和信号和进位信号的输出
full_adder.v

module full_adder
(
    input   wire    in_1,
    input   wire    in_2,
    input   wire    cin,

    output  wire    sum,
    output  wire    cout
);

wire    half1_sum;
wire    half1_cout;
wire    half2_cout;

half_adder half_adder_inst1
(
    .in_1(in_1),
    .in_2(in_2),
    
    .sum (half1_sum),
    .cout(half1_cout)
);

half_adder half_adder_inst2
(
    .in_1(half1_sum),
    .in_2(cin),
    
    .sum (sum),
    .cout(half2_cout)
);

assign cout = half1_cout | half2_cout;

endmodule

代码编写完成之后就要进行代码的编译,来检测我们的语法错误。

2.5 编译代码

首先要新建工程。工程的建立我们已经很熟悉了

Zy9wClLMpr

工程建立完成之后,加入我们的顶层文件,接下来进行代码的编译

20231024224155_KD6jWatwqf

这里出现了 4 个报错信息,我们来看一下。报错信息提示我们,找不到 half_adder 这个模块,也就是说你虽然实例化了,但是并没有把这个模块加入到工程之中,所以说识别不了,我们还需要将半加器的 .v 文件,添加到我们的全加器工程当中。半加器的文件添加完成之后,我们再次进行编译

20231024224434_JiQiOF8s3m

2.5.1 RTL 视图查看

编译通过,只有警告信息,我们点击 OK。我们来查看一下我们的 RTL 视图

20231024224710_oncDaHTmjl

我们可以看到 RTL 视图与我们绘制的整体框图是一模一样的,我们放到一起对比一下

模块框图和RTL视图对比

相信大家看到 RTL 视图之后,对层次化设计的思想应该有了更深入的理解。
代码编译通过之后,我们就开始进行仿真验证。

2.6 逻辑仿真

第一步就是仿真文件的编写。我们找到 sim 文件夹,建立我们的仿真文件

eCJTKH4ICB

全加器的仿真文件和半加器的仿真文件,结构应该是差不多的,只是多了进位信号的模拟输入,还有就是实例化的不同

20231024232445_myq5yFuSvN

仿真文件编写完成之后,我们保存。然后将仿真文件加入到我们实验工程当中

20231024232709_7vJWBT63W2

然后是仿真设置

20231024234456_JrT3D6nasO
开始仿真

20231024234735_5mnOJ0CGiX

我们打开我们的 sim 选项卡。在 sim 界面中,可以看到整个仿真的结构:最上头是仿真模块,接下来是顶层模块;打开顶层模块的 + 就可以看到它内部实例化的模块,就是我们的半加器,在波形界面默认只会添加仿真模块的波形。我们回到 sim 界面,添加我们顶层模块的波形,和我们实例化的半加器模块的波形。然后将信号全选、进行分组,我们之前讲到了:使用左下角的这个按钮,可以对各信号的前面路径进行一个屏蔽

20231024235159_aX3Xh5ceCD

同样的我们双击模块的名称,也可以对模块名称进行一个更改。我们可以把它们改成最简的形式,方便我们的观察。点击 OK 就保存了,我们使用同样的方法,对其他几个模块名称进行修改

20231024235408_SFXqWFQQFK

因为我们在仿真设置当中对仿真停止时间进行了一个设置,所以说,波形在自动运行 1us 之后,它就会进行一个停止。如果说我们使用默认的选项,不进行时间参数的设置,它就会一直进行波形的仿真,除非你点击 Stop,它才会停止。

我们点击 Restart 按钮,删除所有的波形,然后时间参数设置为 100us,运行一次,进行全局的视图

20231024235705_ipOzIyS1uL

然后添加我们的参考线,进行局部的放大,我们就可以对仿真波形进行一个验证

20231024235829_iStkADyHhR

我们随机选取一个位置。

菜单栏 Wave–>Wave Preferences…–>Display–>Enable/Disable–>Waveform selecting highlighting 勾选,打开选中波形高亮设置项。

那么第一路输入信号为高电平,第二路输入信号是低电平,进位信号也是低电平。两路信号输入到第一个半加器,那么输出的求和信号是高电平,进位信号是低电平。这个是正确的

20231025001208_yXT5l8lfnh

第一个半加器输出的求和信号和输入到全加器的进位信号,输入到第二个半加器模块,那么一个为高电平,一个为低电平,所以说第二个半加器输出的求和信号是高电平,第二个半加器输出的进位信号是低电平,高电平的求和信号直接输出,这儿是正确的

20231025001604_ffE3gyv5VX

那么两个进位信号进行或运算。第一个进位信号是低电平,第二个进位信号也是低电平,或运算之后还是低电平。那么低电平输出,这儿也是正确的

20231025002208_V6wXRWPmJj

我们再随机选取一个位置,比如说这个位置

image-20231025003531367

两路输入信号都是高电平,进位信号也是高电平。
两个输入信号输入到第一个半加器当中,都为高电平,所以说,第一个半加器输出的进位信号是高电平,求和信号是低电平。
第一个半加器输出的求和信号低电平与输入到全加器的进位信号高电平,作为输入信号输入到第二个半加器当中,一个是高电平、另一个是低电平,所以说,第二个半加器产生的求和信号是高电平,进位信号是低电平。
那么求和信号高电平直接作为全加器的求和输出,这儿是高电平,没有问题。
第一个半加器输出的进位信号是高电平,第二个半加器输出的进位信号是低电平,两个信号进行或运算,输出的还是高电平,这儿没有问题。

接下来,查看一下打印信息

20231025010801_Mhy78axE9o

经过验证,我们可以发现,我们的代码是正确的。仿真验证通过。

2.7 管脚绑定

接下来进行管脚的绑定。
我们使用按键作为全加器的输入信号和进位信号的输入,使用 LED 灯作为最终的结果信号和进位信号的输出。按键 1 作为输入信号 1 的输入,它的引脚是 M2,点击回车;按键 2 作为输入信号 2 的输入,它的引脚是 M1,点击回车;按键 3 作为输入进位信号的输入,它的引脚是 E15,点击回车

20231025004009_9pkeuRH97j

输出的结果位,使用 D6 LED 灯进行表示,它的引脚是 L7。那么在这个位置,我们可以在这儿直接编写 L7,然后点击回车就可以进行引脚的绑定。同时我们还可以采用拖动的方法进行管脚的绑定,我们可以直接选中输出的结果位信号,然后按住它拖拽,拖拽到 L7 这个位置,然后它俩就进行了绑定。使用同样的方法,我们可以绑定输出的进位信号。我们可以选中它,将它拖拽到我们的 M6 位置。这样就完成了引脚的绑定

20231025004837_F9F2cRNZ7h

引脚绑定完成之后进行一次全编译,点击 OK

20231025005046_VW2JRXzCmk

2.8 上板验证

参照下图连接我们的下载器和我们的电源,下载器的另一端连接到我们的电脑

上板验证前的硬件连线

然后打开我们的电源,回到我们的实验工程,点击这个位置打开下载界面,然后添加我们的文件,进行程序的下载

20231025005849_L4S4Wr28yh

全加器 上板验证

那么以上就是本小节的全部内容。

本小节通过全加器的一个例子,介绍了如何利用层次化的设计方法来进行一个项目的设计。全加器的例子虽然是比较简单,但是它有足够的代表性。在基础部分我们对层次化设计的要求并不高,因为我们的例子都是十分简单的,大多一个或者几个模块就可以实现整个的设计;但是在后面的强化和高级的部分,会有数十个以上的模块,到时候你就能够体会到层次化设计的好处,对层次化设计的了解也会更加的深入。希望大家通过不断的学习,能够熟练掌握这种层次化设计方法。

参考资料:


10-第九讲-层次化设计

7. 层次化设计

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值