之前我在学习的过程中,在找资料的过程中花费了大量时间,但也找到一些大佬的优质文章,为了减少各位粉丝查找优质资源的时间,在这里开创一个转载个人认为内容比较优质的文章,学习一下大佬阅读、分析手册的思路,提升对FPGA底层、应用的知识。
如果觉得对你有帮助,可以订阅该专栏,后续会一直转载优质内容。
以下为大佬文章正文,如有侵权,请通知删除:
所使用EDA软件:VIVADO2019.1.3
FPGA型号:xc7a35tcsg325-2
看完这篇文章你将收获以下内容:
1、理解MUX中输出与地址码,数据输入的关系。
2、理解硬件描述语言与MUX的对应关系。
3、理解FPGA中用LUT6组成的MUX4:1。LUT6和MUXF7,MUXF8组成的MUX8:1,MUX16:1。
说实话上一节课的DRAM,移位寄存器的确是让有些同学脑阔疼滴。那么这一节我们学点简单点的内容放松下----MUX(多路数据选择器),其基本结构如图1。
它是四选一数据选择器(MUX4_1),这里的四指的是数据输入有4位(D0-D3),一指的是数据输出有1位(Q),地址码是用来决定将哪一位输入传送到输出。表1是MUX4_1的对应表,它阐明了输出与地址码,数据输入的关系。将地址码(A)二进制的转为十进制,这个十进制的结果就决定将哪一路的输入(D)传送到输出(Q)。
接下来我们来看一看FPGA用LUT6做成MUX4_1的(图2红框部分)。
其实MUX4_1本质上还是一个LUT6,只不过它有着特定的连线方式,它将2位地址输入,以及4位数据输入连入到LUT6的地址输入端。从前面的课程中,我们知道LUT6的输出O6是由6位地址输入以及存放在LUT6中的64个初始值决定的。现在我们已经知道了LUT6的地址输入,那么只要配上相应的初始值就可以做成MUX4_1了,那么它的初始值是什么呢?下面我们通过例1以及它所导出的网表看一下LUT6里的初始值。
//以下是例1
module MUX4_1(
input [3:0]d,
input [1:0]a,
output reg q
);
always@(*)begin
case(a)
2'b00 : q <= d[0];
2'b01 : q <= d[1];
2'b10 : q <= d[2];
2'b11 : q <= d[3];
endcase
end
endmodule
//以下是例1导出的网表,这里本来是有IBUF,OBUF的,为避免混淆视线我把它删了
LUT6 #(
.INIT(64’hF0AAFFCCF0AA00CC)
)
q_OBUF_inst_i_1(
.I0 ( d[1] ),
.I1 ( d[0] ),
.I2 ( d[3] ),
.I3 ( a[1] ),
.I4 ( a[0] ),
.I5 ( d[2] ),
.O ( q )
);
很不幸,综合后的结果不是d0d3对应LUT6的I0I3,a0a1对应LUT6的I4I5。而是乱序的,木有办法,我们就通过python的代码将这个值给求出来并与网表中的INIT值对比,同时加深对MUX的印象。
代码思路如下:
当a[1:0]=2’b00时,d[3:0]=4’bxxx0对应的INIT位或上0,d[3:0]=4’bxxx1对应的INIT位或上1
当a[1:0]=2’b01时,d[3:0]=4’bxx0x对应的INIT位或上0,d[3:0]=4’bxx1x对应的INIT位或上1
当a[1:0]=2’b10时,d[3:0]=4’bx0xx对应的INIT位或上0,d[3:0]=4’bx1xx对应的INIT位或上1
当a[1:0]=2’b11时,d[3:0]=4’b0xxx对应的INIT位或上0,d[3:0]=4’b1xxx对应的INIT位或上1
因为INIT或上0还是INIT ,所以我们只关注INIT位或上1的情况。
#coding=UTF-8
I = range(0,64)
result=0x0000000000000000
def calc_shift(i):
add=0
add+=20*((i>>0)&0b000001)
add+=21((i>>1)&0b000001)
add+=2**2((i>>2)&0b000001)
add+=23*((i>>3)&0b000001)
add+=24((i>>4)&0b000001)
add+=2**5((i>>5)&0b000001)
return add
#!!!特别要注意对应关系 i[0]->d[1] , i[1]->d[0]
#i[2]->d[3] , i[3]->a[1] , i[4]->a[0] , i[5]->d[2]
for i in range(0,64):
if(i&0b0110100b000010): #a[1:0]=2’b00 ,d[0]=1’b1
result|=1<<(calc_shift(i))
elif(i&0b0110010b010001): #a[1:0]=2’b01 ,d[1]=1’b1
result|=1<<(calc_shift(i))
elif(i&0b1110000b101000): #a[1:0]=2’b10 ,d[2]=1’b1
result|=1<<(calc_shift(i))
elif(i&0b0111000b011100): #a[1:0]=2’b11 ,d[3]=1’b1
result|=1<<(calc_shift(i))
# %#x代表16进制输出
print("result is %#x \n"%result)
#打印结果:result is 0xf0aaffccf0aa00cc
从python代码运行后结果为0xf0aaffccf0aa00cc,与INIT的64’hF0AAFFCCF0AA00CC相同,初始化值是怎么来的我相信上面的代码已经阐述的挺清楚的,我就不过多累赘了。
然后我来解释一下用2个LUT6加一个MUXF7组成一个MUX16_1的情况,MUX8_1的结构如图3红框部分所示。
看到这里,难免会有小伙伴会好奇,为什么这里明明是8位数据输入端,理应3位地址输入就够了,然而却会有4位地址(SELDC[2:0]+SEL D[2:0]),下面我们用一个例子(例2)以及它综合后的原理图(图4)来讲解有关MUX8_1有关地址输入的连线。
//以下是例2
module MUX8_1(
input [7:0]d,
input [2:0]a,
output reg q
);
always@(*)begin
case(a)
3'b000 : q <= d[0];
3'b001 : q <= d[1];
3'b010 : q <= d[2];
3'b011 : q <= d[3];
3'b100 : q <= d[4];
3'b101 : q <= d[5];
3'b110 : q <= d[6];
3'b111 : q <= d[7];
endcase
end
endmodule
从图4我们看到,3位地址线,低2位(蓝线,黄线)是连到两个LUT6的同一位置的(也就是说2个LUT6的4个地址输入只占用了MUX8_1的低两位地址),高1位是连到去MUXF7的数据选择端。两个LUT6各做为MUX4_1,8位数据输入经过2个MUX4_1后选剩2位数据,这2位数据作为MUXF7的输入I0 I1,再由a[2]决定将哪一位输出到O。简单而言就是从8个里面选2个,再从2个里面选1个。
MUX16_1的结构(图5)与MUX8_1类似,先用低2位地址将16数据通过4个MUX4_1选出4个,再通过MUXF7在4个数据中选2个(用次高位地址),最后再通过MUXF8在2个数据中选1个(用最高位地址),从而实现16选1的功能。就是16个里选4个,再从4个里选2个,再从2个里选1个。
说到这里可能就有人会问为什么不先用4个LUT6在16个里选4个,再用1个LUT6从4个里选1个呢。这是一个好问题,我们先看看这两种方法在FPGA内部的实现方式,红线是只用LUT6,绿线是LUT6+MUX结构。
我们也很容易看出只用LUT6(红线)这种做法做无法保证从输入到输出走线延迟的一致性,走的线越长造成线与线之间延迟偏差就会越大,因而会造成多路输出信号到下一级输入时间不一致,到达时间不一致很容易产生毛刺,造成逻辑错误。这也是为啥F7MUX和F8MUX做与LUT6靠的那么,还做成对称结构的原因,它本身就是为了多路选择器设计的。
这节课讲到这里也差不多结束了。相信很多人上大学时的与硬件描述语言或者EDA的第一堂实验课都是MUX。
然而在那时,很多同学都是拿着老师的模板复制,粘贴,运行,烧录,灯亮起来。再然后,就没有然后了。当然也会有探索精神的同学会去理解地址码与数据输入的关系,但这是极少部分。所以在这里我必须吐槽下大学里的挺多实验课,很多老师都是只要求学生按着步骤做,得出结果就好,中间极少讲述其中原理。
长期下来就会让很多同学就会认为上个实验课,就是按着操作说明书接接线,调调仪器,得出结果,最后写份报告而已。这种类型的实验课是有违高等教育的初衷的,本人希望后面会有所改善吧。同时,我也希望本科里的学生能够认真地去对待实验课,课程设计。
毕竟这应该是大多数学生把课本所学的知识转换到实践当中为数不多的机会,只有在这些实验,课设中将所学知识消化了,吸收了,才能取得更大的进步。
如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!
如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!