欢迎来看我的VHDL学习笔记,这是我写的第一篇,是关于我在用元件递归来设计n输入与门的全过程。
上个月没有时间主要是懒,所以没有达成每月至少一篇的小目标,这个与争取多写几篇。
设计n输入与门的想法是在做8421BCD计数器实验的时候冒出来的。设计这个计数器是用的D触发器,所以需要用到三输入的与门和或门。然后,我就联想到,如果是n输入的与门该怎么实现呢?
1 元件递归设计
首先声明一点,元件递归的设计不是正常的设计,而且不可综合。
元件(component)是重复使用entity的一种方式,可以在此基础上设计出通用的实体在项目中使用,具体语法请查阅IEEE Std 1076-2019。
在上数电课的时候,应该都知道设计多级逻辑门的时候最好要设计比较平衡的结构,以降低延迟带来的影响。所以,我在设计n输入与门的时候也想往这个方向靠。但是事实证明我这个考虑似乎是多余的,见下一章
为了实现平衡的设计,我准备使用二分法。方案一,通过generate循环,使用二维数组存储每次二分的结果,循环次数可以通过$n = \lceil log_{2}{m} \rceil$得到;但这个方案感觉比较麻烦,选择放弃。
方案二,利用元件自己调用自己,通过递归来实现二分法。首先,将输入端口数port_num为1或者2作为递归结束条件,直接使用and操作符输出结果;然后,针对输入端口数port_num为奇数和偶数分别进行操作,将二分后的结果连接到信号re中,最后对re做与操作输出到outp。代码如下。
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
entity add_gate is generic ( port_num : integer := 6 ); port ( inp : in unsigned(port_num - 1 downto 0); outp : out std_logic ); end add_gate;
architecture behavioral of add_gate is component add_gate is generic ( port_num : integer := 6 ); port ( inp : in unsigned(port_num - 1 downto 0); outp : out std_logic ); end component;
signal re : unsigned(1 downto 0) := "00"; begin
gen : if port_num = 1 generate outp <= inp(0); elsif port_num = 2 generate outp <= inp(0) and inp(1); elsif port_num mod 2 = 1 generate add1 : add_gate generic map (port_num => port_num / 2 + 1) port map (inp => inp(port_num / 2 downto 0), outp => re(0)); add2 : add_gate generic map (port_num => port_num / 2) port map (inp => inp(port_num - 1 downto port_num / 2 + 1), outp => re(1)); outp <= re(0) and re(1); else generate add1 : add_gate generic map (port_num => port_num / 2) port map (inp => inp(port_num / 2 - 1 downto 0), outp => re(0)); add2 : add_gate generic map (port_num => port_num / 2) port map (inp => inp(port_num - 1 downto port_num / 2), outp => re(1)); outp <= re(0) and re(1); end generate;
end behavioral;
|
简单写了个仿真文件,仿真结果如下。
当然了,元件递归是不可综合的,综合是会出现如下的弹窗。
2 正常的n输入与门设计
一般来说,设计n输入与门是不会使用二分法的。虽然思路没错,但是,真的没有必要,原因我会在后面提到。下面两段代码都是比较正常的而且可以使用的n输入与门的设计。
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
| library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
entity add_gate is generic ( port_num : integer := 6 ); port ( inp : in unsigned(port_num - 1 downto 0); outp : out std_logic ); end add_gate;
architecture behavioral of add_gate is signal tmp : unsigned(port_num - 1 downto 0); begin
tmp(0) <= inp(0); gen : for i in 1 to port_num - 1 generate tmp(i) <= tmp(i - 1) and inp(i); end generate; outp <= tmp(port_num - 1);
end behavioral;
|
示例一使用for/generate将后加入的输入信号和之前的结果进行与操作,RTL原理图如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
entity add_gate is generic ( port_num : integer := 6 ); port ( inp : in unsigned(port_num - 1 downto 0); outp : out std_logic ); end add_gate;
architecture behavioral of add_gate is begin
outp <= '1' when inp = to_unsigned(2 ** port_num - 1, port_num) else '0';
end behavioral;
|
示例二利用了n输入与门的实质,即所有输入都为高电平才输出高电平,否则输出低电平,RTL原理图如下。
接下来,就要说说为什么二分法没有必要了。很显然,示例一的RTL图是一点都不平衡的设计,如果在实际连接中使用这个结构势必会出现延时的问题。但是,如果综合这两个示例,如下所示,就会发现这两个示例的RTL原理图虽然大相径庭,但是他们的综合后原理图是一模一样的。图中的LUT(查找表)是FPGA的基本组成单元,可以当做是一个n*1的RAM。以图中的6输入LUT为例,LUT内部存储了$2^n$个1bit的数据,地址是6位的;输入信号是地址总线,LUT根据输入的地址查找到相应地址的数据,将数据直接输出到LUT的输出端口上。
3 总结
虽然这次用了元件递归来设计n输入与门才发现元件递归是不能综合的 :&(蛆音娘_扶额) ,并且综合工具的优化功能是很强大的,但是,这次尝试真的特别有意思,让我对FPGA的设计有了新的关注点。之后的学习过程中,我准备将之前学过的算法设计思想添加到FPGA设计中,找到新的乐趣。 :@(击掌)
最后的最后,请别忘了一键三连收藏分享。