1. FPGA内部硬件结构与代码的关系

代码和硬件之间的映射关系是一个很奇妙的过程,也展现出人类的智慧。单片机内部的硬件结构都是固定的,无法改变,我们通过代码操作着内部寄存器的读写,来执行各种复杂的任务。FPGA的硬件结构并不像单片机一样是固定不变的,而是由更加原始的基本逻辑单元构成,我们需要用HDL语言来描述我们要实现的功能,而并不需要 关心硬件的结构是如何构建的,我们通过使用FPGA厂商的综合器来将HDL所描述的功能代码映射到我们的FPGA基本逻辑单元上,而这个映射的过程是综合器帮我们自动完成的,我们并没有直接用语言去操作这些基本逻辑单元,这也可以理解为什么HDL叫硬件描述语言,而不是硬件语言的原因。

我们使用Verilog语言来描述功能,用Altera CycloneIV系列的EP4CE10F17C8芯片来验证下面的例子,观察我们编写的Verilog代码综合后到底映射到了哪些硬件结构上。我们通过察看RTL Viewer、Technology Map Viewer(Post Mapping)、Chip Planner来得出验证分析结果。

注:RTL Viewer:寄存器级的视图,包括原理图视图,同时也包括层次结构列表,列

出整个设计网表的实例、基本单元、引脚和网络。主要体现的是逻辑连接关系和模块间的结构关系,和具体的FPGA器件无关。

Technology Map Viewer(Post Mapping):将RTL所表达的结构进行优化,增加或减少一些模块,包括一个原理视图以及一个层次列表,列出整个设计网表的实例、基本单元、引脚和网络。更接近于最后底层硬件映射的结果,以便于映射到具体的FPGA器件上。

Chip Planner:可以看作是版图模型,用于查看编译后布局布线的详细信息,显示器件的所有资源,例如,互连和布线连线、逻辑阵列块(LAB)、RAM 块、DSP 块、I/O、行、列以及块与互连和其它布线连线之间的接口,真实的表达所使用的资源以及在芯片中的相对位置信息。还可以实现对逻辑单元、I/O 单元或 PLL 基元的属性和参数进行编译后编辑,而无需执行完整的重新编译。

1.1. I/O的映射

我们的例子是给一个输入信号,然后不进行任何逻辑运算直接输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module line
(
input wire in,

output wire out
);

assign out = in;

 endmodule

代码编写完后点击“Start Analysis & Synthesis”图标进行分析和综合。

FPGAha002

图 76‑1

RTL Viewer

双击“Netlist Viewers”下的“RTL Viewer”查看RTL视图。

FPGAha003

图 76‑2

因为代码就是对RTL级层次的描述,所以RTL视图只有一根连线,这和我们代码设计的思想结果是完全一致的。

FPGAha004

图 76‑3

Technology Map Viewer(Post Mapping)

双击“Netlist Viewers”下的“Technology Map Viewer(Post Mapping)”。

FPGAha005

图 76‑4

我们可以看到和RTL视图不同的是输入端口和输出端口分别加上了buffer,这是我们代码中并没有的设计,是综合器优化后自动加上的。

FPGAha006

图 76‑5

双击上图中蓝色的buffer可以看到下图中三角形的缓冲器。

FPGAha007

图 76‑6

Chip Planner

点击“Start Compilation”图标进行全编译,此过程会进行布局布线。

FPGAha008

图 76‑7

点击“Chip Planner”图标打开版图模型,在“Flow Summary”报告中也可以看到全编译后的详细信息,其中只使用了两个管脚资源。

FPGAha009

图 76‑8

Chip Planner打开后的界面如图 76‑9所示,我们要找到代码最后映射到版图模型中的具体位置,可以在右上角红色框处的“Find what”处搜索定位,如果没有找到“Find what”,按住键盘“Ctrl + F”就会自动出现。

FPGAha010

图 76‑9

在图中红色区域中的“Find what”搜索RTL代码中的信号名“in”,然后点击“List”。FPGAha011

图 76‑10

继续点击“Go Next”。

FPGAha012

图 76‑11

红色区域中深蓝色的小矩形块就是输入信号“in”所映射到版图模型中的位置

FPGAha013

图 76‑12

在选中“in”所映射的模块的前提下点击图中左侧“Generate Fan-Out Connections”图标可以将从该位置扇出的连线显示出来,一直点击此扇出线会一致追踪下去。

FPGAha014

图 76‑13

我们看到连线从“in”处开始,到”out“结束,并将”in“和”out“连到一起,这也就说明了代码中的“in”和“out”之间确实是用一根导线连接的。图中的①相当于我们外部的管脚输入进来信号,经过内部连线,从管脚②输出到外部。我们分别双击①和②打开模块内部观察其映射的结构。

FPGAha015

图 76‑14

我们看到了一个完整的IOE内部结构,其中蓝色显示的是真实使用到的结构,灰色的是未使用到的结构。图 76‑15是输入的,图 76‑16是输出的。

FPGAha016

图 76‑15

FPGAha017

图 76‑16

我们根据信号的流向进行标号,①和⑦是PAD,为硅片的管脚,封装在芯片内部,用户看不到,PAD的输入输出往往和外部的Pin有一段连线。②和⑥分别是输入缓冲器和输出缓冲器,我们在Technology Map Viewer(Post Mapping)视图中就已经看到过,这里功能上是一样的,只是具体的实现结 构不同;③是一个输入延时模块,是用来调节输入信号的相位延时(在静态时序分析中会详细讲解其使用方法),右键点击该模块可以设置延时的时间,这是综合布局布线工具自动给我们加的(当输入信号绑定到时钟管脚上时就不会自动连接到输入延时模块上),并不是我们在RTL代码中设计的;④⑤⑧是和外部管脚以及其他层连接的接 触点,可以理解为相当于是PCB中的过孔,⑧这里用于连接到和外部信号输入的管脚Pin上,④和⑤在内部通过导线连接到一起。

FPGAha018

图 76‑17

FPGAha019

图 76‑18

上面的操作我们并没有进行管脚的绑定约束,是开发工具自动给我们分配到一个任意位置的管脚,如果约束了具体的管脚,其在Chip Planner中映射的位置还会变化,但结构基本相同。

综合器在帮我们自动完成综合和布局布线的过程中会根据我们的HDL代码与实际的功能来做一些适当的优化,这些优化是为了让整个映射后的硬件更加适配具体的FPGA器件,所以有些时候我们用HDL描述的功能并不是我们所认为的会使用到那部分基本逻辑单元,而是进行了优化后的结果,这些优化包括:面积的优化、速度的优化、 功耗的优化、布局布线的优化、时序的优化等。FPGA开发工具同样也给用户预留了一些可供用户优化的选项设置,但这都要在用户能够熟练掌握开发工具和内部结构的前提下才能够实现。

1.2. 组合逻辑的映射

大家可能会有这样的疑问,我们编写的Verilog代码最终会在FPGA上以怎样的映射关系来实现功能呢?我们以一个最简单的组合逻辑与门为例来向大家说明。RTL代码如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
module and_logic(
input wire in1 ,
input wire in2 ,

output wire out
);

//out:输出in1与in2相与的结果
assign out = in1 & in2;


endmodule

代码编写完后依然需要点击“Start Analysis & Synthesis”图标进行分析和综合。然后双击“Netlist Viewers”下的“RTL Viewer”查看RTL视图。我们可以看到两个输入信号经过一个与门后输出,和我们代码设计的结果是完全一致的。

FPGAha020

图 76‑19

点击“Start Compilation”图标全编译进行布局布线,然后打开Chip Planner视图。Chip Planner打开后的界面图 76‑20所示,我们可以看到在版图模型中有一个块蓝色区域的颜色变深,说明有该区域的资源被占用,在FPGA内部硬件结构简介中我们知道这是一个逻辑阵列块LAB,我们将该区域放大。

FPGAha021

图 76‑20

如图 76‑21所示,放大后可以看到蓝色变深的区域中有16个小块,这16个小块就是LE,其中只有一个LE的颜色变是蓝色的,说明该处的资源被使用了,双击蓝色的LE即可观察其内部的结构。

FPGAha022

图 76‑21

打开LE后内部的结构如图 76‑22所示,其中蓝色显示的是真实使用到的结构,灰色的是未使用到的结构,我们可以看到有两个输入和一个输出,与RTL代码的描述是对应的,红色框就是查找表LUT。

FPGAha023

图 76‑22

大家可能还是不理解LUT是如何实现我们的与逻辑。我们先来看一下与逻辑的真值表,如下所示:

表格 76‑1

输入

输出

in1

in2

out

0

0

0

0

1

0

1

0

0

1

1

1

根据真值表可以看出输入,两个输入对应的输出一共有4种情况,LUT需要做的工作就是能够根据输入的变化对应输出正确的值。我们可以LUT预先存储所有输出的4种情况,然后判断输入,对应输出就可以了。LUT的内部结构在Chip Planner中并没有表达出来,但是我们可以推断出来,如图 76‑23所示,为与门 所对应的LUT内部结构图,其中LUT中存储的是4种输出情况,输入信号in1和in2通过多路器选择哪一个存储在LUT中的值输出。图中展示的是当in1和in2输入的值都为1时,我们可以看出存储在LUT的“1”从标注的红色路径中输出到out,LUT中存储的值会在综合工具综合时进行映射。这里我们也不难看出L UT其实所充当的角色就是存储器RAM的功能,更直白一点说LUT就是个小RAM,所以我们也可以用LUT来构成小规模的RAM用于存储数据,LUT所构成的RAM就是我们常说的Distribute RAM,简称为DRAM。

FPGAha024

图 76‑23

1.3. 时序逻辑的映射

组合逻辑和FPGA之间的映射关系我们知道了,那时序逻辑和FPGA之间又是一种怎样的映射关系呢?我们就以前面寄存器章节的例子来向大家说明,也一同把当时为什么用异步复位更节约资源的原因告诉大家。我们先来看一下同步复位D触发器的RTL代码,如下所示:

同步复位D触发器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
module flip_flop(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_in ,

output reg led_out
);

//led_out:led灯输出的结果为key_in按键的输入值
always@(posedge sys_clk)
if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else
led_out <= key_in;

endmodule

代码编写完后依然需要点击“Start Analysis & Synthesis”图标进行分析和综合。然后双击“Netlist Viewers”下的“RTL Viewer”查看RTL视图。如图 76‑24所示,可以看到一个 D触发器的结构,也可以称为寄存器,还附加了一个选择器,用于同步复位的控制,和代码设计的结果是完全一致的。

FPGAha025

图 76‑24

点击“Start Compilation”图标全编译进行布局布线,完成后我们可以看到“Flow Summary”资源使用量,如图 76‑25所示,可以看到使用了LE中的一个组合逻辑资源和一个时序逻辑资源。

FPGAha026

图 76‑25

然后打开Chip Planner视图,如图 76‑26所示,我们可以看到在版图模型中有一个块蓝色区域的颜色变深,说明该区域的资源被占用,在FPGA内部硬件结构简介中我们知道这是一个逻辑阵列块LAB,我们将该区域放大。

FPGAha027

图 76‑26

如图 76‑27所示,放大后可以看到蓝色变深的区域中有16个小块,这16个小块就是LE,其中只有一个LE的颜色发生了变化,这次不仅有蓝色,还有红色,说明该处的资源被使用了,双击这个LE即可观察其内部的结构。

FPGAha028

图 76‑27

打开LE后内部的结构如图 76‑28所示,其中蓝色显示的是真实使用到的结构,灰色的是未使用到的结构,我们可以看到①、②、③为三个输入,其中③为时钟的输入端,然后一个输出,和RTL代码的描述是对应的。因为我们设计的是时序逻辑,所以这次我们可以发现比组合逻辑多出来的结构主要是红色框所表示的寄存器。

FPGAha029

图 76‑28

在该视图中点击下面复位信号的名字后会看到在LE的内部结构图中用绿色标注路径,如图 76‑29所示,可以知道①为复位信号的输入端,②为key_in信号的输入端。

FPGAha030

图 76‑29

我们再来看一下异步复位D触发器的RTL代码,如下所示:

异步复位D触发器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
module flip_flop(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_in ,

output reg led_out
);

//led_out:led灯输出的结果为key_in按键的输入值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else
led_out <= key_in;

endmodule

代码编写完后依然需要点击“Start Analysis & Synthesis”图标进行分析和综合。然后双击“Netlist Viewers”下的“RTL Viewer”查看RTL视图。如图 76‑30所示,我们可以看到一个 D触发器的结构,和代码设计的结果是完全一致的。FPGAha031

图 76‑30

点击“Start Compilation”图标全编译进行布局布线,完成后我们可以看到“Flow Summary”资源使用量报告,如图 76‑31所示,可以看到只使用了LE中的一个时序逻辑资源。

FPGAha032

图 76‑31

然后打开Chip Planner视图,如图 76‑32所示,我们可以看到在版图模型中在同样的位置也有一个块蓝色区域的颜色变深,说明该区域的资源被占用,在FPGA内部硬件结构简介中我们知道这是一个逻辑阵列块LAB,我们将该区域放大。

FPGAha033

图 76‑32

如图 76‑33所示,放大后可以看到蓝色变深的区域中有16个小块,这16个小块就是LE,其中只有一个LE的颜色发生了变化,也有蓝色和红色,说明该处的资源被使用了,双击这个LE即可观察其内部的结构。

FPGAha034

图 76‑33

打开LE后内部的结构如图 76‑34所示,其中蓝色显示的是真实使用到的结构,灰色的是未使用到的结构,我们可以看到①、②、③为三个输入,其中③为时钟的输入端,②仍为key_in的输入端,而复位信号①的位置则发生了变化,直接连到了寄存器上。

FPGAha035

图 76‑34

看到这里我们不禁会有两个疑问,异步复位D触发器LE内部结构图明明显示使用了查找表,为什么却在“Flow Summary”资源使用量报告中却显示没有使用该部分资源?为什么同步复位D触发器比异步复位D触发器多使用了一部分资源呢?

首先来说第一个问题,虽然异步复位D触发器LE内部结构图明明显示使用了LUT,但是几乎没有任何逻辑需要使用LUT,相当于通过查找表将key_in信号连接到寄存器的输入端,所以在“Flow Summary”资源使用量报告中显示没有使用该部分资源。

然后是第二个问题,如图 76‑35所示,我们将寄存器部分的视图放大来看,可以发现该寄存器本身就包含一个异步清零信号“aclr”,且该清零信号还标识为“!ACLR”,也就是低电平有效,这下我们终于明白了为什么代码中使用异步低复位了,因为这部分资源本来就有,不需要额外创造,而如果我们使用同步高复位,就会 增加额外的逻辑,需要使用LUT资源,所以同步复位D触发器比异步复位D触发器多使用了一个LUT,大家可以试想一下如果我们使用的很多同步复位D触发器的时候就会占用很多不必要的LUT资源,从而造成资源的浪费,希望大家以后在编写代码的时候注意。

FPGAha036

图 76‑35

其实在FPGA的开发中理想情况下FPGA之间的数据要通过寄存器输入、输出,这样才能使得延时最小,从而更容易满足建立时间要求。我们在FPGA内部硬件结构中得知,IOB内是有寄存器的,且IOB内的寄存器比FPGA内部的寄存器更靠近外部的输出管脚,这样就能够得到更小的延时,从而使时序更好。我们可以看到在没 有指定的情况下寄存器的映射都是随机的,那么问题来了,如何才能指定寄存器映射到IOB中呢?我们依然用异步复位D触发器的例子来给大家演示。

如图 76‑36所示,我们回到工程界面点击“Assignment Editor”图标来约束寄存器映射的位置。

FPGAha037

图 76‑36

如图 76‑37所示,在打开的“Assignment Editor”界面中点击“To”下面的“<<new>>”添加要约束的项。

FPGAha038

图 76‑37

点击如图 76‑38所示的望远镜图标,打开“Node Finder”。

FPGAha039

图 76‑38

在打开的“Node Finder”界面中我们找到信号的输入key_in,如图 76‑39所示,根据序号顺序,在①处的“Named :”选项框中输入“*”,点击 ②处的“List”,在③处的“Node Found :”列表中就会列出名为key_in 的信号,双击③处的key_in 信号或点击图标④,key_in 信号就被添加到⑤处的“Selected Nodes:”中了。如果我们想取消⑤处选择的信号则在“Selected Nodes:”选中该信号后点击图标⑥即可。设置完毕后点击“OK”退出。

FPGAha040

图 76‑39

如图 76‑40所示,设置“Assignment Name”,下拉列表找到“Fast Input Register(Accepts wildcards/groups)”,这是设置将寄存器映射在输入IOB中的约束,如果设置将寄存器映射在输出IOB中则选择“Fast Output Register(Accepts wildcards/groups)”。

FPGAha041

图 76‑40

如图 76‑41所示,设置“Value”的值为“on”。

FPGAha042

图 76‑41

全部设置完成后的结果如图 76‑42所示。

FPGAha043

图 76‑42

点击“Start Compilation”图标全编译进行布局布线,否则无法重新映射资源。此时会弹出如图 76‑43所示的对话框,提示是否要保存更改,选择“Yes”后会执行布局布线。

FPGAha044

图 76‑43

当布局布线重新完成映射后我们再来看看Chip Planner视图,如图 76‑44所示,我们发现整个视图都没有什么明显的变化,难道是映射失败了?

FPGAha045

图 76‑44

如图 76‑45所示,既然不能用肉眼直接看到,那我们可以在Chip Planner界面右上角红色框处的“Find what”处搜索定位信号在版图模型中的位置,如果没有找到“Find what”搜索框,按住键盘“Ctrl + F”就会自动出现。

FPGAha010

图 76‑45

在图 76‑46中红色区域中的“Find what”搜索RTL代码中的信号名“key_in”,然后点击“List”。

FPGAha046

图 76‑46

点击图 76‑47所示的“key_in”可以看到在版图模型的对应位置高亮显示,这个位置就是FPGA的IOB区域。

FPGAha047

图 76‑47

如图 76‑48所示,将映射的IOB区域放大,其中①为我们key_in的输入端,而②则是寄存器所映射的新位置。

FPGAha048

图 76‑48

如图 76‑49所示,我们双击②处的寄存器,观察其内部结构,发现IOB中的寄存器已经高亮显示了,说明真的映射上了,实现了我们的要求。

FPGAha049

图 76‑49

1.4. 指定PLL的映射位置

既然我们可以指定寄存器放在IOB内,那我们同样也可以指定PLL的位置。首先要确保我们有多个PLL才行。如图 76‑50所示,我们所使用的EP4CE10F17C8芯片刚好有两个。

FPGAha050

图 76‑50

为了演示这个例子,我们使用pll工程,RTL代码如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module pll
(
input wire sys_clk , //系统时钟50MHz

output wire clk_mul_2 , //系统时钟经过2倍频后的时钟
output wire clk_div_2 , //系统时钟经过2分频后的时钟
output wire clk_phase_90 , //系统时钟经过相移90°后的时钟
output wire clk_ducle_20 , //系统时钟变为占空比为20%的时钟
output wire locked //检测锁相环是否已经锁定,
 //只有该信号为高时输出的时钟才是稳定的
 );

 ////
 //\* Instantiation \//
 ////

 //------------------------pll_ip_inst------------------------
 pll_ip pll_ip_inst
 (
 .inclk0 (sys_clk ), //input inclk0

 .c0 (clk_mul_2 ), //output c0
 .c1 (clk_div_2 ), //output c1
 .c2 (clk_phase_90 ), //output c2
 .c3 (clk_ducle_20 ), //output c3
 .locked (locked ) //output locked
 );

 endmodule

代码编写完后依然需要点击“Start Analysis & Synthesis”图标进行分析和综合。然后双击“Netlist Viewers”下的“RTL Viewer”查看RTL视图。

FPGAha051

图 76‑51

点击“Start Compilation”图标全编译进行布局布线,然后打开Chip Planner视图。Chip Planner打开后的界面如图 76‑52所示,我们可以看到在版图模型中左下角有一块颜色变深的区域,与之形成鲜明对比的是右上角颜色没有变深的位置,这就是我们FPGA芯片中两个PLL的位置,而颜色变深的区域说明资源被占用。

FPGAha052

图 76‑52

放大并点击该PLL,如图 76‑53所示,可以在右侧看到该PLL的结构图中显示的部分蓝色高亮信号,下面“Location”则显示了该PLL的名字为“PLL_1”。

FPGAha053

图 76‑53

如图 76‑54所示,选中该PLL后点击左侧的图标显示扇入扇出线路径,可以看到PLL在芯片内的连接关系。

FPGAha054

图 76‑54

如图 76‑55所示,我们回到工程界面点击“Assignment Editor”图标来约束PLL的位置。

FPGAha055

图 76‑55

如图 76‑56所示,在打开的“Assignment Editor”界面中点击“To”下面的“<<new>>”添加要约束的项。

FPGAha056

图 76‑56

在打开的“Node Finder”界面中我们找到信号的输入key_in,如图 76‑57所示,根据序号顺序,在①处的“Named :”选项框中输入“pll”,点击 ②处的“List”,在③处的“Node Found :”列表中就会列出名为altpll:altpll_component的信号,双击③处的altpll:altpll_component 信号或点击图标④,altpll:altpll_component信号就被添加到⑤处的“Selected Nodes:”中了。如果我们想取消⑤处选择的信号则在“Selected Nodes:”选中该信号后点击图标⑥即可。设置完毕后点击“OK”退出。

FPGAha057

图 76‑57

如图 76‑58所示,设置“Assignment Name”,下拉列表找到“Location(Accepts wildcards/groups)”,这是设置位置的约束。

FPGAha058

图 76‑58

如图 76‑59所示,点击“Value”下的“…”。

FPGAha059

图 76‑59

如图 76‑60所示,在弹出的“Location”对话框中的“Element:”选择“PLL”。可以看到在这里我们还可以设置其他元素的位置。

FPGAha060

图 76‑60

如图 76‑61所示,“Location:”选择“PLL_2”。

FPGAha061

图 76‑61

如图 76‑62所示,“Location”对话框设置完毕后点击“OK”

FPGAha062

图 76‑62

全部设置完成后的结果如图 76‑63所示。

FPGAha063

图 76‑63

点击“Start Compilation”图标全编译进行布局布线,否则无法重新映射资源。此时会弹出如图 76‑64所示的对话框,提示是否要保存更改,选择“Yes”后会执行布局布线。

FPGAha044

图 76‑64

当布局布线重新完成映射后我们再来看看Chip Planner视图,如图 76‑65所示,我们可以发现在版图模型的右上角一块颜色变深的区域,与左下颜色没有变深的位置形成鲜明的对比,颜色变深的区域说明资源被占用。

FPGAha064

图 76‑65

放大并点击该PLL,如图 76‑66所示,可以在右侧看到该PLL的结构图中显示的部分蓝色高亮信号,下面“Location”则显示了该PLL的名字为“PLL_2,说明已经成功映射上了。

FPGAha065

图 76‑66

如图 76‑67所示,选中该PLL后点击左侧的图标显示扇入扇出线路径,可以看到PLL在芯片内的连接关系。

FPGAha066

图 76‑67

修改PLL的映射位置意义何在呢?当我们的时序在某些情况下不好的时候就可以通过修改PLL的映射位置来调整时序,以实现时序的收敛。