Conway在FPGA的生活游戏

2021-06-06 06:52:56

学习新的编程语言时,我喜欢有一个明确的尚未琐碎的问题来解决。 Conway的生活游戏(GOL)适合这个定义。它有足够的深度来揭示各种权衡。如此自然,当我拿起凿子硬件描述语言(HDL)时,我想在FPGA中建立生活游戏。它结果比软件更有趣。这篇文章将以我的进度写入Chisel和Verilog代码,以在DigiLent Arty A7上运行GOL,并在VGA屏幕上看到实时模式。

凿子在源自伯克利和RISC V社区的相对较新的HDL中。它使用Scala编程语言作为基础,并将HDL定义为特定于域的语言。从本质上讲,凿子只是一套Scala图书馆。这允许应用通用编程语言的全部功率来产生高阶硬件抽象。这种方法似乎与传统的HDL(例如Verilog)(如Verilog)的发展方式几乎相反。 Verilog最初专注于描述硬件 - 非常接近可以通过传统示意图和后后添加的通用编程元素表示的内容来创建更复杂的组件。

在软件中实现GOL的琐碎方法是迭代每个细胞。在硬件上,相反 - 琐碎的方法是为每个单元提供专用内存和计算单元。然后将单元单元放置在网格上并连接到其八个邻居。在每个时钟周期上,该网格立即计算所有单元的下一个状态。

类别单元格扩展模块{val IO = IO(新捆绑{val enable =输入(bool())val neighbors = vec(8,输入(bool()))val状态=输出(bool())val writebable =输入( bool())val writestate =输入(bool())})val状态= reginit(false。b)(io。启用){何时(io。witherbable){state:= io。 WRITESTATE}。否则{val count = io。邻居 。折叠(0. 0(3. W))((x:bool,y:uint)=> x。asuint + y)(计数< 2.u){state:= false。 B}。否则(count === 2. u){状态:= state}。其他(count === 3. u){state:= true。 B}。否则{状态:= false。 b}} io。州:=州}

单元格模块输入已启用,写入,Wregestate和八个邻居状态。启用时,1个单元格在每个时钟周期中计算其下一个状态,或者当写入为1时,将Wreitest的级别设置为新状态。启用时为0,单元格保持状态不变。唯一的模块输出是当前状态。

输入/输出束后跟状态寄存器,时钟并由凿子隐式地重置。接下来是一种组合电路,其将具有邻居的GOL规则计算为作为输入和新状态作为输出。

最有趣的行是生成一系列添加剂以计算邻居计数的一行。 Scala的折叠式允许非常简洁地表达这一系列,这瞥见了凿子的力量。以下是Xilinx Vivado阐述的结果示意图(它是可点击的)。

Warning: Can only detect less than 5000 characters

模块VGA_LIFE(输入线SYSCLK,输出REG HS,VS,输出reg [3:0] R,G,B);参数Frame_Width = 1024;参数frame_height = 768;参数h_fp = 24; //前门廊宽度(像素)参数H_PW = 136; //同步脉冲宽度(像素)参数h_max = 1344; //总周期(像素)参数v_fp = 3; //前门廊宽度(线)参数v_pw = 6; //同步脉冲宽度(线)参数v_max = 806; //总周期(行)参数h_pol = 0;参数v_pol = 0;电线CLK,CLK_FEDBACK,PIALEL; reg [11:0] HC,VC; plle2_base#(。clkfbout_mult(7.8),。clkin1_period(10.0),。clkout0_divide(12),。clkout0_phase(0.0))genclock(clkout0(clk),。clkfbout(clk_feedback),。clkin1(sysclk),。pwrdwn( 1&#39; b0),。第一个(1&#39; b0),。clkfbin(clk_feedback));栅格网格(时钟(VS),。重置(0),。IO_ReadState(像素),。IO_Readrow(VC [9:4]),。IO_Readcol(HC [9:4]));初始开始HC = 0; Vc = 0;结尾 ;始终@(提出CLK)IF(HC ==(H_MAX-1))HC&lt; = 0;否则HC&lt; = hc + 1;总是@(提出clk)if((hc ==(h_max - 1))&amp;(vc ==(v_max - 1)))Vc&lt; = 0;否则if(hc ==(h_max -1))vc&lt; = vc + 1;总是@(提出clk)if((hc&lt; frame_width)&amp;(vc&lt; frame_height))开始r <{4 {pixel}}; g&lt; = {4 {pixel}}; B&lt; = {4 {pixel}};最终否则开始r <= 0; g&lt; = 0; B&lt; = 0;结束总是@(提出clk)if((hc&gt; =(h_fp + frame_width - 1))&amp;(hc&lt;(h_fp + frame_width + h_pw-1)))hs&lt; = h_pol;否则HS&lt;! H_POL;总是@(提出clk)if((v_fp + frame_height - 1))&amp;(vc&lt;(v_fp + frame_height + v_pw - 1)))vs&lt; = v_pol;否则vs&lt; =! v_pol;终点

在Verilog中编写的这个简单的顶级模块将其全部连接在一起。它实例化了GridInit和PLL,以产生1024x768 @ 60Hz模式所需的时钟。每个单元占16像素的平方。网格与VGA垂直同步脉冲有时时钟,以确保我们每次屏幕刷新一次或每秒60帧时使用GOL规则。 VGA信号形成寄存器的速度快65MHz更快地产生各个像素。

我使用Vivado在Xilinx Artix-7 XC7A35T-1CSG324部分上合成和放置GOL设计。 Vivado报告20,800 LUT可用于实施,正如您所看到的,64到48 Gol网格占所有可用LUT的90%。

Vivado还估计我们设计的功耗,总共为0.381瓦。

您可能已经注意到VGA颜色用4位编码,也称为RBG444编码。我使用了DigInent的VGA PMOD将其转换为VGA所需的模拟水平。

我捕获了一个慢动作的视频,能够看到单个GOL世代。每秒60帧,它令人叹息地快速。但更重要的是,记住我们根据我们可以渲染屏幕的速度设置电网时钟。计算GOL规则的电路中的传播延迟确定真正的速度限制。这种瓶颈由于将电池连接到其邻居的电线长度来构成更复杂的问题,以便将细胞拓扑放置在FPGA拓扑中,以便将电池连接到其邻居的电线的长度占该延迟的有意义的分数。

作为一个实验,我将网格的时钟设置为在电路板的外部振荡器,以100MHz运行。当网格每秒达到1亿代,VGA显然无法跟上。每个帧都包括超过一百万代的像素在屏幕上创建这种美丽的图案。

总之,我希望量化两个GOL的琐碎实施。一个人作为现代桌面CPU的软件程序运行,另一个在廉价FPGA上以硬件实现的另一个。首先,我会将原始速度与延迟进行比较,以计算单个单元格的新状态,而第二个,其估计能量。这两个计算都是“背部的餐巾纸”,只旨在获得级别的感觉。

我的台式电脑有一个六核处理器,能够突破4GHz。此处理器具有L1缓存,具有4个周期延迟。我们将假设GOL Universe足够小,以完全适合L1缓存。因此,下一个州的计算可能由L1访问读取读取八个邻居状态并读/写状态本身。

谈到能源时,我的台式机处理器具有65瓦的热设计功率。这意味着它每秒消散65焦耳的能量。让我们假设我们利用所有六个核心来计算GOL。

当我改变设计使用100MHz时钟为FPGA网格时,Vivado报告了一个适度的电量增加到0.444瓦特。