构建您自己的FPGA

2020-08-24 02:50:42

公开赛7400逻辑竞赛是一项众包竞赛,参赛标准简单而宽泛:用离散逻辑芯片制造有趣的东西。它现在已经是第二年了,这一次我受到了进入它的灵感。

对于任何不熟悉的人来说,离散逻辑是许多IC家族中的任何一个,每个IC都执行单一的、通常相当简单的功能。典型的离散逻辑IC包括基本逻辑门,如AND、OR和NAND、触发器、移位寄存器和多路复用器。对于较小的元件,如门和触发器,单个IC通常包含几个独立的元件。正如你可以想象的那样,从离散逻辑中构建任何复杂的东西都需要使用大量的部件;如今,它们通常被用作粘合逻辑的胶水,而不是作为一流的组件,它们在很大程度上已被专用器件、微控制器和FPGA的组合所取代。

从离散逻辑中构建微控制器或CPU是一种流行的业余追求,它有一个有用的目的:从头开始构建CPU教会您很多关于CPU体系结构和权衡的知识;这是一项有趣而有启发性的练习。所以,我想知道,用离散逻辑构建FPGA不也同样有教育意义吗?因此,我的参赛作品是:一块完全由离散逻辑芯片制成的FPGA(或者更确切地说,是一片)。

FPGA最基本的构建块是单元或片。通常,片有几个输入、一个查找表(或LUT)(可编程为评估这些输入上的任何布尔函数)和一个或多个输出,每个输出可配置为在输入更新时立即更新(异步),或仅在下一个时钟滴答时更新(同步),使用片中内置的触发器(同步)。一些FPGA单元具有附加功能,例如硬件中实现的加法器,以节省使用LUT来实现此目的。

片的核心,查找表,看起来近乎神奇-接受一组输入,它可以被编程为计算它们上的任何布尔函数并输出结果。然而,顾名思义,它的实现非常简单,而且它也是一种用于实现微码和其他可配置粘合逻辑的技术。原则上,您要做的是:拿一个存储器IC,比如一些SRAM或EEPROM。将地址线连接到输入,将数据线连接到输出。现在,输入状态的任何组合都将被解释为地址,存储器将查找该地址并在数据输出上提供该地址。通过使用您想要计算的函数的状态表对内存进行编程,您可以将其配置为计算任何您喜欢的值。

不幸的是,7400系列存储器已经不再生产,虽然有大量的SRAM和EEPROM可用,但可用的最小尺寸远远大于我们对简单离散FPGA的要求。此外,为了能够对内存进行编程和读取,我们需要大量的逻辑来在写入内存和从内存读取之间切换(在单个端口的内存上,这些都使用相同的引脚)。

然而,一个简单的解决方案出现了:移位寄存器!移位寄存器实际上是一个8位存储器,具有串行输入(方便我们的目的),并且每一位都暴露在自己的引脚上。通过将其与8路多路复用器相结合,我们得到了基本的3输入1输出LUT。我们的LUT可以使用数据、时钟和锁存线重新编程,其中许多可以链接在一起并串联编程。8路多路复用器上的3个选择输入形成LUT的输入,多路复用器的输出位为输出。因此,在两个现成的7400系列IC中,我们有一个完整的查找表。

对于我们的FPGA片,我们将使用这些离散的LUT中的两个,它们的输入组合在一起。为什么是两个?因为3个输入和2个输出的组合能力几乎是您可以用来实现有趣事情的最小能力。3个输入和2个输出使您可以在单个片中构建一个全加器;任何较少的输入或输出以及仅将两个1位数字与进位相加就需要多个片,这严重限制了我们的能力。

下一个组件是触发器,以及用于选择异步或同步模式的逻辑。有丰富的触发器和寄存器可供选择,从2个到8个,在单个IC中,并且有各种控制方法,所以不成问题。在同步和异步之间做出选择有点困难。这里的自然选择是2路多路复用器,但是虽然存在具有多个2路多路复用器的芯片,但它们都将选择线组合在一起,这意味着您必须为芯片中的所有多路复用器选择相同的输入。显然,这不太适合我们的申请。

幸运的是,构建双向多路复用器并不困难。有几种选择,但最有效的是使用三态缓冲区。在7400范围内有一对-74*125和74*126最符合我们的要求。每个芯片都包含四个三态缓冲器,两个芯片之间的唯一区别在于,一个芯片在使能线为高时使能其输出,而另一个芯片在使能线为低时使能其输出。通过将它们成对组合在一起,我们可以创建多路复用器;每个IC中的一个可以为我们提供四个独立的多路复用器。两个多路复用器,加上我们的寄存器IC,就可以得到同步/异步选择逻辑。当然,我们需要一种方法来控制多路复用器,所以在另一个移位寄存器中链接以提供一些状态来对它们进行编程。

现在我们已经设计了一个基本芯片的核心,让我们来看看任何FPGA的第二个主要组件:路由。灵活的布线是任何有用的FPGA的关键属性;如果没有良好的布线,您就无法在需要的地方获得信号,并且浪费了宝贵的资源,从而使您的FPGA用处大大降低。不过,路由需要使用大量资源才能正确实现。我们最少能提供多少才能得到有用和有趣的结果?

通常,FPGA将各个切片放置在矩形网格中。公交车在栅格中的切片之间水平和垂直运行。切片能够进入其交叉处的某个线子集,并且同样可以输出到某个线子集。通常,总线可以不间断地继续通过切片,也可以中断总线,从而有效地在切片的两侧创建单独的总线。在某些情况下,总线还可以以其他方式连接在一起,在不同总线线路之间或在水平和垂直总线之间布线,而不直接涉及片。

一位总线即使对于我们的目的来说也有点太窄了;很多有趣的应用程序需要更多的东西,所以让我们看看垂直和水平的2位总线是怎么回事。许多FPGA在一个方向或另一个方向上包含内置偏置;这通过支持更常见的用途来节省布线资源,但代价是使不太常见的设置更昂贵。在我们的例子中,我们将使从左侧和顶部总线读取数据变得更容易,也更容易写入右侧和底部总线。我们可以通过在左侧、顶部和右侧的每条总线上安装2输入多路复用器来做到这一点;这些多路复用器馈送到我们的LUT';的3个输入。对于输出,我们可以使用更多的三态缓冲器,以允许一个LUT输出到一条或两条正确的总线线路,而另一条LUT输出到一条或两条底部总线线路。要从底部读取,或者驱动左侧或顶部线路,只需驾驶相反的一侧,并闭合适当的总线开关即可。

说到总线开关,我们将采用最简单的配置:一个连接每条顶线和底线的开关,以及一个连接每条左线和右线的开关,这些开关可以单独打开或关闭。74*4066&34;四路双边开关IC在单个IC中提供了一种便捷的方式来实现这一点。当然,我们所有的路由都需要状态-3位用于输入多路复用器,4位用于输出使能,4位用于总线开关-因此我们将使用另一个移位寄存器,以及我们添加的用于同步/异步选择的一些备用位。

布线完成后,我们或多或少已经在离散逻辑中设计了整个基本的FPGA芯片。让我们盘点一下:

这是总共12个离散逻辑IC来实现一个中等能力的FPGA芯片。添加几个LED提供总线状态的可视指示器,以及一些边缘连接器将它们连接在一起,我们就有了一个可以组合在一起的矩形配置的电路板,从而形成了一个模块化的、可扩展的离散逻辑FPGA。没有意义,因为它的能力是中等价位的FPGA或CPLD芯片的一小部分?可能吧。凉爽的?。非常肯定。

当然,如果没有办法对其进行编程,那么拥有DFPGA是没有好处的。我们可以自己找出位掩码来实现我们想要的东西,但是这样做既繁琐又容易出错。将VHDL或Verilog移植到这样的东西上将是困难的,而且考虑到我们正在处理的切片数量,这将是巨大的过度杀伤力。相反,我选择实现一种简单的硬件描述语言,我称之为DHDL。

DHDL并不尝试处理布局或优化;相反,它实现了一个相当简单的编译器来获取逻辑表达式并将其转换为片配置数据。DHDL文件由一组切片定义组成,后跟要调用的切片列表,排列方式与DFPGA布局相同。以下是纹波进位全加器片的DHDL定义示例:

切片加法器{ L0^R1^u0->;R0; (L0&;r1)|(l0&;u0)|(R1&;u0)->;d0; }。

这里,10、R1等是指公交线路-';u';、';d';、';l';和';r';表示向上、向下、向左和向右。两个加数提供在L0和L1上,因为总线开关是闭合的,所以它们也在R0和R1上可用,加法器利用了这一点,因为我们一次只能从左边的一条总线线路中进行选择。进位输入通过总线U0进入。第一个表达式计算两个输入和进位的和,并在R0上输出。第二个表达式计算进位输出,该进位输出通过d0传输到下一个切片。

DHDL在这里为我们负责总线开关配置:默认情况下,所有总线开关都是闭合的(即,它们导通),但是当我们输出到总线线路时,相应的总线开关缺省为打开。在这种情况下,这是正确行为,因为它允许我们读取l0上的一个加数,并在r0上输出结果;它还确保我们分离传入和传出进位信号。

在某些情况下,我们可能希望自己配置总线。我们可以使用表达式`a b`来指定总线开关应该打开,使用表达式`a<;->;b`来指定应该关闭它。以下是利用以下各项的存储元件的示例:

切片存储{ (u0&;r1)|(!u0&;l0)同步->;r0; L0<;->;R0; }。

此片使用反馈来存储值,方法是将值输出到R0,然后从l0读回该值。由于输出到R0通常会导致编译器打开l0和r0之间的开关,我们显式地告诉它我们希望关闭开关,从而使反馈成为可能。该定义还演示了如何使用`sync`或`async`关键字在赋值操作符之前指定同步与异步行为。默认值为异步。因此,该片将输出r0和l0上的存储值;在u0为高的时钟周期的前沿,它将存储l1/r1的值作为新值。还要注意,因为我们没有输出到d0,所以u0和d0之间的开关是闭合的,这意味着我们可以垂直堆叠其中的许多,并通过启用输入来控制它们。我们已经有效地制作了一个人字拖切片。

让我们看看完整的FPGA定义是什么样子。下面是一个4位密码锁:

切片存储{ (u0&;r1)|(!u0&;l0)同步->;r0; L0<;->;R0; } 切片比较_进位{ !(L0^R1)&;u0->;d0; } 切片比较{ !(L0^R1)->;d0; U0d0; } 存储比较, 存储比较进位, 存储比较进位, 存储比较进位。

首先,我们定义一些片-我们已经看到的存储片,以及一个比较器,当两条水平总线线路相等且其u0输入为1时,比较器输出1到d0。我们还定义了一个不带进位的比较器版本,因为最上面的片没有进位输入。

操作过程如下:要设置代码,请在最左边的每个切片的L1输入上输入值,然后在一个时钟周期内将顶部切片的u0输入设为高电平。要测试组合,请再次输入L1输入上的值,但将顶部切片的u0输入保持为低电平。右下角切片的d0行指示组合是否正确。

最后,让我们尝试一些更复杂的东西:PWM控制器。我们需要一个计数器、一些比较器和一个设置/复位电路:

切片切换器{ !r1sync->;r1; R1->;d0; } 切片计数器{ R1^u0同步->;r1; R1&;u0->;d0; } 切片比较{ !(L0^R1)->;d0; } 切片比较_进位{ !(L0^R1)&;u0->;d0; } 切片溢出_通过{ U0->;r0; } 切片锁存{ (R0|u0)&;!L0同步->;R0; } 切换比较, 计数器COMPARE_CARRY, 计数器COMPARE_CARRY, OVERFLOW_PASS锁存器。

前两个切片定义Toggler和Counter共同实现了一个二进制计数器。触发器是最低有效位,而任意数量的计数器级可以垂直链接,以形成n位波纹进位计数器-在这种情况下,我们构造了一个3位计数器。COMPARE和COMPARE_CARRY从前面的草图中看起来应该很熟悉;它们实现了一个纹波进位比较器,在这种情况下,将二进制计数器的输出与另一条总线线路进行比较,该总线线路将使用开关进行设置。OVERFLOW_PASS的工作非常简单-它将溢出信号从计数器传递到其正确的输出,使该信号和比较器输出都可用于最终切片srlatch。顾名思义,这是一个简单的设置/复位锁存器,计数器溢出对其进行复位,比较器对其进行设置。

通过设置3个输入位以反映所需的占空比,并使时钟线脉冲足够快,锁存器片的R0输出将以适当的占空比进行PWMed-当该总线线路上的LED变暗时,可以直观地观察到该占空比。

设计和建造这块板是一项有趣的练习。由于集成电路的数量和希望使PCB尽可能紧凑,这是迄今为止我设计的最难布线的电路板。由于我赶在比赛前把电路板送去组装的时间有限,我最终第一次使用了自动外设。Eagle的Autorout非常糟糕,但事实证明,有一个更好的免费选择,叫做Freerouting。Freerouting是一个基于Java的PCB路由器;它可以从Eagle、KiCad和其他公司导入布局,并导出可以执行以在CAD工具中实现最终布线的脚本。Eagle希望生产150多个通孔的电路板,Freerouting能够生产出不到50个通孔的电路板,目测显示它也相当健全。它也不只是用于自动布线-它有一个出色的手动布线模式,在这种模式下,它将允许你轻推现有的轨道,而不必在每次你需要插入另一条线路时将它们撕开并重新布线。

至于捏造,我选择了优秀的哈克瓦纳,他为我组成了20块板,并以创纪录的时间将它们送到了我手中。来自Farnell/Element14的一大批零件订单让我对零件进行了分类,剩下的就是几个小时的焊接-每个电路板上有200多个SMT焊盘,组装过程需要一段时间。

当然,与任何第一次迭代设计一样,也存在问题。我几乎立刻想到了几个小的设计改进,这将在一定程度上增加电路板的能力,让您确定串行编程流如何在电路板之间连接的跳线可以放置得更好。更有问题的是,我不小心把所有移位寄存器的复位线都拉低了,因为它们实际上是低电平的--它们应该连接到5V导轨上。经过一些试验,然而,我想出了一个绿色布线的解决方案,你可以在下面的照片中看到;它甚至不会增加每个电路板超过几分钟的施工时间。当然,这个错误已在原理图中修复。

它一旦组装好并正常工作后会是什么样子?非常酷。它可能没有真正的FPGA那么有能力,但它也更容易检查和理解。通过所有总线线路上的LED,您可以随时准确地看到内部状态,这使得调试变得容易得多。

这里是阵列中的一个电路板,已经完全构造好并连接好了;点击图片了解更多信息。

所有设计文件,以及DHDL编译器、测试套件和演示定义都是Apache2.0许可下的开源文件。你可以在这里的Github上找到它们。如果您决定构建自己的DFPGA,或者发现原理图或代码在您自己的项目中有用-请让我知道!

还记得一开始我对专用内存芯片的使用不屑一顾,说现在所有可用的内存芯片都太大了,太难连接了吗?嗯..。这并不像我设计东西时想象的那样准确。

你可以得到的内存确实比我们需要的要大,但这在适度的时候可能是一个优势--这意味着构建更有能力的切片是可能的。您想要一个具有8个输入和4个输出的片,它可以输出到任何总线线路,并且具有4位的内部状态,从而允许它在每个片中实现16个状态机?而且所有这些芯片的数量都是上面设计的一半多一点?事实证明,通过几个聪明的把戏,这应该是可能的-只需抓住一个。

问题是:可用的最小SRAM是256千比特,这真的很大,以至于像Arduino这样的嵌入式处理器在没有外部存储器的情况下甚至无法对这些片中的任何一个进行编程。我们可以改用EEPROM,它往往稍小一些,可以很容易地提前编程,但这仍然需要一种存储其他配置位的方法,例如输出使能(OUTPUT ENABLE)。不幸的是,EEPROM移位寄存器似乎并不真正存在。

不过,通过一点巧妙的优化,可以实现在上电时从EEPROM加载输出使能状态的紧凑型设计,尽管这比当前的设计稍微复杂一些。不幸的是,我怀疑对离散逻辑FPGA的需求-即使是相当有能力的-也是很低的,所以这种设计不太可能出现在白天。

不过,我可能错了。您想要您自己的分立FPGA吗?你能想出它的实际用途吗?请在评论中让我知道!

上一篇文章下一篇博客评论由Disqus提供支持