背景
-
本系列于好友千歌离开上海后开始写起,
至今2024年10月21日,正好三个月了。
现在,是时候更新了。 -
基于Xilinx的7系列FPGA,
在通过文档《ug474_7Series_CLB.pdf》学习CLB过程中,
对CLB中的查找表(Look-Up Table,LUT)进行展开学习,遂就有了此文。 -
本文实际上刚刚已经发布过一遍了,
但千歌的建议:插入一节,讨论如何把LUT6拆开使用,或者插入,不同级别LUT等效比。
因此,笔者在原文基础上,增添了<2.2 ··· 不同级别LUT等效比>这一小节,致千歌!
系列目录与传送门
基于Xilinx的7系列FPGA,笔者在对CLB进行学习、研究时,归纳并总结出了以下博文:
1、分篇1:《7系列 之 查找表(Look-Up Table,LUT)》
2、分篇2:《7系列 之 存储单元(Storage Elements)》
3、分篇3:《7系列 之 多路复用器(Multiplexers)》
4、分篇4:《7系列 之 进位逻辑(Carry Logic)》
5、分篇5:《7系列 之 分布式RAM(Distributed RAM)》
6、分篇6:《7系列 之 7系列 之 移位寄存器(Shift Registers)》
7、总纲 :《7系列 之 可配置逻辑块(7 Series Configurable Logic Block,CLB)》
说明1:本系列基于文档文档《ug474_7Series_CLB.pdf》而写,翻译和感悟,会出现中英文混合的情况。
说明2:虽然文中会出现一些原文的部分截图,但非常支持并推荐大家去看原汁原味的官方文档
说明3:在查阅相关资料过程中,发现一些关于相关知识点的介绍零零散散,本系列会对其进行整合,力求详尽。
说明4:如果文章有误,欢迎大家指出、讨论,笔者也会积极改正,希望大家一起进步!
一、总述
1.1 ··· LUT的概念
查找表(Look-Up-Table,LUT),属于FPGA中实现逻辑功能的核心组件,非常重要。
查找表,顾名思义,可以简单暴力地理解为是一种可以查询内容的表;再文明一点的理解就是:一种存储预定义结果的数据结构,可以实现一定数量输入的布尔函数。
1.1.1 ··· LUT的Verilog代码
// Vivado中的LUT模块的调用格式:
// 6 input lookup-table
LUT6_2 #(
.INIT(64'h0000000000000000) // Specify LUT Contents
) LUT6_2_inst (
.O6(O6), // 1-bit LUT6 output
.O5(O5), // 1-bit lower LUT5 output
.I0(I0), // 1-bit LUT input
.I1(I1), // 1-bit LUT input
.I2(I2), // 1-bit LUT input
.I3(I3), // 1-bit LUT input
.I4(I4), // 1-bit LUT input
.I5(I5) // 1-bit LUT input (fast MUX select only available to O6 output)
);
// 通过参数INIT输入预定义的结果,再通过I0、I1、I2、I3、I4、I5的输入值从预定义的结果中选取对应的结果进行输出。
// 通过修改n输入LUT的INIT值,n输入LUT可以表示所有n位输入或者n位以内输入的布尔函数(或者门电路),这恰巧体现了LUT的灵活性。
// 如果LUT资源的数量越多,那么所能够实现的功能就越复杂。
1.1.2 ··· LUT的结构图
(博客截图,道友们可以去看看原文,原文链接为:http://t.csdnimg.cn/WJQPC)
1.1.3 ··· LUT在Vivado中的Device视图
以xc7a100tfgg676-2 (active) 为例,
在Vivado的Device视图中放大并进行观察,如下图所示。
上图,是对 SLICE_X30Y78(SLICEM)的Device视图中的LUT6放大截图。
上图,是对 SLICE_X31Y78(SLICEL)的Device视图中的LUT6放大截图。
将上述两种LUT的结构进行对比,可得:
1、 相同点:都具有地址输入线(A1-A6),两个输出口(O5-O6)。
2、不同点:SLICEM的LUT6具有写地址输入线(WA1-WA8),写数据端(DI1 DI2),写使能端(WE),而SLICEL的LUT6没有。
1.2 ··· LUT的本质
LUT本质是一个RAM(Random Access Memory,随机存取存储器),目前是基于SRAM(Static Random Access Memory,静态随机存取存储器)工艺的。一个6位输入LUT,即LUT6,可以被看成一个有6位地址线的大小为64x1的RAM。
LUT可以将n位输入数据作为n位RAM的地址,然后通过该n位地址找到对应的值,并将该值作为结果进行输出。可以理解为,LUT的RAM结构,存储了输入值与输出值之间的对应关系,即真值表。那么,真值表对应的便是上述代码中的参数INIT。
查阅资料时,发现有一些博主在博客里说LUT本质是一个ROM(Read-Only Memory,只读存储器)。笔者在这里的理解是:
1、他们是站在了LUT工作时(或是被读取数据时)的角度来看待LUT的,此时的LUT就是一个ROM。
2、SLICEL中的的LUT只可以被读取数据,不能被写入数据,只能实现基本的查找表逻辑。
其实,正是SLICEL、SLICEM中的LUT6的不同,才使得SliceL的LUT只具有存储数据的能力,只能作为ROM使用,而SliceM的LUT还具备了数据写入的功能,可以作为DRAM或移位寄存器使用。
也在一个博主的评论区看到了相应的问题互动,如下图所示。
RAM与ROM虽然在 读写操作 和 掉电后的效果 上有所差异。但在这里,我们只需要弄清LUT的原理或本质就好。
在好多篇博客和文章里面都看到过这样的一句话,现引用过来供道友们参考:
”当用户通过原理图或HDL语言描述了一个逻辑电路以后,PLD/FPGA开发软件会自动计算逻辑电路的所有可能的结果,并把结果事先写入RAM,这样,每输入一个信号进行逻辑运算就等于输入一个地址进行查表,找出地址对应的内容,然后输出即可“
除此之外,还看到这样一句话,忘了保留原文出处了,等想起来了在加上吧:
”在设计阶段,设计者可以使用硬件描述语言(如Verilog或VHDL)编写逻辑,编译器会自动将这些逻辑映射到查找表中。“
二、7系列中的LUT
2.1 ··· 文档《ug474_7Series_CLB.pdf》中的LUT
-
7系列FPGA中的LUT可以被配置成 一个具有单输出的六输入LUT6 ,或者 两个具有单独输出但共用同样地址或逻辑输入的五输入LUT5 ,每个五输入LUT 的输出可以选择性地寄存在一个触发器(flip-flop)中。
-
7系列中,函数生成器(function generators) 可用六输入查找表LUT6实现。一个Slice中的四个LUT6(分别用A、B、C、D编号)中的每个LUT6有六个独立的输入(分别用A1至A6的编号表示),两个独立的输出(分别用O5、O6两个编号表示)。
-
逻辑单元与六输入LUT的数量比为 1.6 :1 。
( 原文:The ratio between the number of logic cells and 6-input LUTs is 1.6:1.) -
下图为ug474_7Series_CLB.pdf的原文截图
函数生成器(function generators) 可以实现:
- 任意的六输入布尔函数
- 两个任意的五输入的布尔函数,只要这两个函数的输入一样
- 两个任意的3输入,或2输入,或更少输入的布尔函数
两个任意的5输入或少于5输入的布尔函数使用:
- A1-A5输入
- A6接高电平
- O5和O6输出
通过一个LUT的传播延迟与所实现的功能无关,来自 function generators 的信号可以:
- 直接出Slice(通过A、B、C、D 输出O6,或通过AMUX、BMUX、CMUX、DMUX输出O5)
- 从O6输出进入专用异或XOR门
- 从O5输出进入进位逻辑链(carry-logic chain)
- 从O6输出进入进位逻辑多路选择器的选择行
- 提供存储单元的D输入(Feed the D input of the storage element)
- 从O6输出到 F7AMUX/F7BMUX 宽多路选择器
除了基本的 LUTs 外,Slices 还包含三个多路选择器( F7AMUX、F7BMUX 和 F8MUX ),这些多路选择器用于组合4个 function generators 来实现 Slice 中7或8个输入的任何函数。
- F7AMUX:用于从LUT A 和LUT B生成7输入函数
- F7BMUX:用于从LUT C和LUT D生成7输入函数
- F8MUX:用于组合所有LUTs来生成8输入函数。
个人理解: 一个Slice有4个LUT6,即可实现4个六输入函数。也就是说,如果需要实现7输入的函数或LUT7,就可以用 F7AMUX 将最下面两个 LUT6(A、B)组合起来,也可以用 F7BMUX 将另外两个 LUT6(C、D)组合起来;如果使用F8MUX将两个LUT7进行组合,即可得到一个8输入函数或者LUT8。 具有8个以上输入的函数可以使用多个Slice来实现,但在CLB中,不能通过Slice之间直接相连,来得到输入个数大于8的function generators。
”但是级联的LUT越多,潜在的组合逻辑时延就越严重,对于系统时延的要求就越苛刻,一般采用寄存器来同步后再将结果输出,以确保输出结果正确。“(原文链接:http://t.csdnimg.cn/eCByP)
”在实际开发中,如果采用多个LUT6级联来实现,当输入位数过多,即组合逻辑复杂时,则会造成级联的LUT6过多,逻辑级数变大大,其结果是通常会造成组合逻辑延时多大,导致时序紧张,因此,在设计中,我们一般采用插入FF的方法来切割组合逻辑,防止级数偶合,使得每一段的逻辑级数都不过于长,从而优化时序。(原文链接:http://t.csdnimg.cn/jzc2N)“
2.2 ··· 不同级别LUT等效比
本节,我们可以通过代码、RTL视图、综合后的视图、资源消耗的角度,
来探讨LUT6、LUT5、LUT4之间的联系。
2.2.1 ··· LUT6
下面这段代码,按照前文所叙述,可以用LUT6来实现。
那么,我们先观察其在Vivado中的RTL视图和综合后的视图。
`timescale 1ns / 1ps
module prj_top(
input din0,
input din1,
input din2,
input din3,
input din4,
input din5,
output dout
);
assign dout = din0 & din1 & din2 & din3 & din4 & din5;
endmodule
RTL视图如下,就是几个简单的与门电路的组合,实际上这个可以直接从Verilog代码中看出来的。
那么,它综合后是什么样子的呢?综合后的视图,如下所示。
不错所料,是个LUT6电路。
那么,如果把其中的 与门 换成 或门,或者是 异或等其它逻辑门,改变代码逻辑如下。
理论上来说,代码综合后的结果依旧是个LUT6电路,只是LUT6里面的真值表发生了改变。
`timescale 1ns / 1ps
module prj_top(
input din0,
input din1,
input din2,
input din3,
input din4,
input din5,
output dout
);
// assign dout = din0 & din1 & din2 & din3 & din4 & din5;
assign dout = ( (din0&din2) ^ din1 | din4 & din5 ^ din3);
endmodule
如果接着改变代码逻辑如下,
理论上来说,RTL电路不会发生太大变化,只是改改门电路的位置与输出,
那么,代码综合后的结果依旧是个LUT6嘛?还是说会发生改变?
`timescale 1ns / 1ps
module prj_top(
input din0,
input din1,
input din2,
input din3,
input din4,
input din5,
output dout0,
output dout1
);
assign dout0 = din0 & din1 & din2 & din3 & din4 & din5;
assign dout1 = din0 & din1 & din2 & din3 & din4 ;
// assign dout = ( (din0&din2) ^ din1 | din4 & din5 ^ din3);
endmodule
其RTL视图如下:
代码综合后的视图为:
哦豁!这份代码综合后的结果为一个LUT6与一个LUT5了,已经发生了改变。
那接下来就掰扯掰扯LUT5。
2.2.2 ··· LUT5
如下,把上述代码中的dout0输出注释掉,可以推测出下列代码的综合结果是个LUT5。
`timescale 1ns / 1ps
module prj_top(
input din0,
input din1,
input din2,
input din3,
input din4,
input din5,
output dout0,
output dout1
);
// assign dout0 = din0 & din1 & din2 & din3 & din4 & din5;
assign dout1 = din0 & din1 & din2 & din3 & din4 ;
// assign dout = ( (din0&din2) ^ din1 | din4 & din5 ^ din3);
endmodule
其代码综合后的结果为:
如下,把上述代码接着做更改,下列代码的综合结果会是两个LUT5嘛?
盲猜不是。
`timescale 1ns / 1ps
module prj_top(
input din0,
input din1,
input din2,
input din3,
input din4,
input din5,
output dout0,
output dout1
);
// assign dout0 = din0 & din1 & din2 & din3 & din4 & din5;
assign dout1 = din0 & din1 & din2 & din3 & din4 ;
// assign dout = ( (din0&din2) ^ din1 | din4 & din5 ^ din3);
endmodule
综合后的结果如下,是两个LUT5?(可能会有道友觉得应该是个LUT6呀!)
道友莫慌,我们先看综合报告:
综合报告里面确实也说了,两个LUT5。
但是,所给出的资源利用里面,也明确地表明只消耗了一个LUT(即LUT6),且 LUT as Logic 。
结论便是:即便综合后的视图显示是两个LUT5,但最终资源消耗里面只占用了1个LUT6。
2.2.3 ··· LUT4
对于LUT4,诸位道友们,咱们也不用装了,直接上代码。
`timescale 1ns / 1ps
module prj_top(
input din0,
input din1,
input din2,
input din3,
input din4,
input din5,
output dout0,
output dout1
);
// assign dout0 = din0 & din1 & din2 & din3 & din4 & din5;
assign dout1 = din0 & din1 & din2 & din3 & din4 ;
// assign dout = ( (din0&din2) ^ din1 | din4 & din5 ^ din3);
endmodule
此时,综合后的结果为:两个LUT4。那么,资源消耗会显示是一个LUT5嘛?
当然不会!7系列里面的LUT资源是LUT6 。且看资源利率用报告,
最终资源消耗里面只占用了1个LUT6。
那么,把代码的输出加到3输出呢?
`timescale 1ns / 1ps
module prj_top(
input din0,
input din1,
input din2,
input din3,
input din4,
input din5,
// output dout0,
output dout1,
output dout2,
output dout3
);
// assign dout0 = din0 & din1 & din2 & din3 & din4 & din5;
assign dout3 = din0 | din1 | din2 | din3;
assign dout2 = din1 ^ din2 ^ din3 ^ din4;
assign dout1 = din0 & din1 & din2 & din3;
// assign dout0 = din1 & din2 | din3 ^ din4;
endmodule
综合结果,是符合预期猜想的。
那么,资源利用呢?是一个LUT6嘛?
不是的,因为一个LUT6最多有两个输出,而且还是作为两个LUT5时才可以的。
因此,当有三个LUT4输出时,那就需要消耗两个LUT6了。
那么,接着该代码,把代码的LUT4输出加到4个输出,理论上是消耗两个LUT6。
`timescale 1ns / 1ps
module prj_top(
input din0,
input din1,
input din2,
input din3,
input din4,
input din5,
output dout0,
output dout1,
output dout2,
output dout3
);
// assign dout0 = din0 & din1 & din2 & din3 & din4 & din5;
assign dout3 = din0 | din1 | din2 | din3;
assign dout2 = din1 ^ din2 ^ din3 ^ din4;
assign dout1 = din0 & din1 & din2 & din3;
assign dout0 = din1 & din2 | din3 ^ din4;
endmodule
代码综合后的结果为:
综合后的资源消耗:
2.2.4 ··· 小结
千歌给我发了一句话:”一般实际使用中,真大佬,会压榨每一个LUT。所以会涉及这个,如何把LUT6拆开使用。如果只写概念,会比较浅。“
三、逛博客时所看到的LUT6
3.1 ··· 逻辑单元的发展历程
- 千歌在《零基础学FPGA(八):可编程逻辑单元(基本结构,Xilinx+Altera)》中对基于多路选择器的逻辑单元、基于PLD结构的逻辑单元、基于查询表的逻辑单元的发展历程有段描述,解释了为什么基于查询表的逻辑单元能成为主流。道友感兴趣的话,可以去看看。
3.2 ··· LUT6的被选择
- 关于LUT4、LUT5、LUT6,谁成为了成为主流的问题,参考下博客《FPGA结构:LUT(查找表)和 MUX(多路选择器)介绍》里面的截图。
- 除了上述截图中的内容,在博客《FPGA原理与结构(2)——查找表LUT(Look_Up_Table)》中,也解释了在如今的主流FPGA中,基本采用的都是6输入的LUT6,不是7输入或者5输入,这是基于面积效率、速度等相关性能的权衡后的结果,介绍的更加详细、全面,如下图所示。
3.3 ··· LUT与真值表、LUT6与LUT5
- 在博客《1.3 7系列FPGA CLB》中有对LUT与真值表、LUT6与LUT5的关系进行总结,可以看一下。
四、总结与补充
在这部分,留下一些LUT学习的后话,留给未来。
-
LUT与传统逻辑门电路
传统门电路的复杂度随着输入逻辑变量数量的增加而增加,
且电路内部的逻辑门、线路的延迟不可避免、也不尽相同,且不同的延迟会引起时序相关问题;
而LUT本质上是通过读取输入信息来选择预设的结果进行输出,因此,每次LUT内部的查找的延迟都是一定的。
这点结合着 章节3.3 中的 LUT与真值表的关系 来看,就会很容易理解,为什么在FPGA中会选择用LUT来代替传统门电路。 -
在博客上看到的两段话
”LUT本质上是一个RAM。当逻辑块实现组合逻辑电路时,查找表中存储的真值表就可以作为小规模的存储器。通常来说,FPGA不可能将所有的查找表都用来实现组合逻辑电路。因此,利用查找表为用户电路实现存储器,既能实现芯片内部的存储功能,又能提高硬件资源使用率。“(这段话的出处,当时忘了记录是在哪篇博客上看到的)
”在Xilinx的FPGA架构中,只有SLICEM的逻辑块里的查找表才能被用作存储器,由查找表构成的存储器称为分布式RAM。分布式RAM能够实现异步访问,但如果使用分布式RAM实现大规模存储器,那么实现逻辑的查找表就会变少。因此,建议仅在需要小规模存储器时使用这种实现方法。“(原文链接:http://t.csdnimg.cn/eiM6G)
-
《ug474_7Series_CLB.pdf》官方文档,建议诸位道友们原汁原味读一遍。
-
这篇CSDN上的博客《FPGA从入门到精通(2) - LUT》写的还不错,与知乎上的《FPGA从入门到精通(2) - LUT》是同一人所写、所发表,后者被引用、借鉴的次数比较多,道友可以去看一看。
-
文档ug474有句话”Device capacity is often measured in terms of logic cells, which are the logical equivalent of a classic four-input LUT and a flip-flop.“,意思是说器件容量通常用逻辑单元来衡量,逻辑单元相当于一个经典的四输入LUT和一个触发器。
-
插个题外话:上次参加了上海慕尼黑电子展,看了下复旦微、高云、紫光同创、智多晶、安路、京微齐力这几家的FPGA产品,他们的产品手册上都会标注芯片所对应的逻辑资源容量,或是直接标注LUT6的资源数量。