verilog2 HDLbits:康威的生命游戏介绍及完整代码思路(Conway‘s Game of Life)

本文详细介绍了康威生命游戏的基本概念和规则,并展示了如何使用Verilog语言实现一个16x16环形网格的版本,通过扩展和计算邻居状态来更新细胞状态,以模拟游戏的演化过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

本文主要介绍了Conway’s Game of Life(康威的生命游戏),并且使用一个16x16 toroid,用verilog实现基本规则,主要通过解决HDLbits的一道问题进行相关知识介绍。


一.Conway’s Game of Life

1.介绍

康威的生命游戏,也被称为生命游戏或简称生命,是英国数学家约翰霍顿康威于 1970 年设计的一种元胞自动机,它是元胞自动机最著名的例子。游戏”实际上是一个零玩家游戏,这意味着它的进化是由它的初始状态决定的,不需要人类玩家的输入,人们通过创建初始配置并观察其演变方式来与生命游戏进行交互。

2.规则

生命游戏的宇宙是一个由方形细胞组成的无限二维正交网格,每个单元(在任何给定时间)都处于两种可能的状态之一,“活”(或“开”)或“死”(或“关”)。 每个细胞都与其八个相邻细胞相互作用,这些邻居是直接水平、垂直或对角线相邻的细胞。在每个时间步骤中,都会发生以下转换:
1)任何活细胞少于两个活邻居都会死亡(称为种群不足或暴露)。
2)任何具有三个以上活邻居的活细胞都会死亡(称为人口过剩或过度拥挤)。
3)任何具有两个或三个活邻居的活细胞都会不变地存活到下一代。
4)任何恰好有三个活邻居的死细胞都会复活。
第一代是通过将上述规则同时应用于种子中的每个细胞而产生的——出生和死亡同时发生,发生这种情况的离散时刻有时被称为tick。换句话说,每一代都是前一代的纯粹函数。这些规则继续被反复应用,以创造更多代。
下面链接有更详细介绍:
conway’s game of life更详细介绍

二.元胞自动机(CA)

元胞自动机(cellular automata,CA) 是一种时间、空间、状态都离散,空间相互作用和时间因果关系为局部的网格动力学模型,具有模拟复杂系统时空演化过程的能力。
由于本文章主要侧重HDLbits题目的介绍解答且个人水平有限,故不介绍。
想简单了解的推荐站内这位博主的介绍,可参考下:
超简单有趣的模拟算法:元胞自动机(CA)原理简介与 matlab 代码实现

三.题目

question
翻译:康威的生命游戏是一个二维的元胞自动机。
“游戏”在二维单元格网格上进行,其中每个单元格要么是 1(活的)要么是 0(死的)。在每个时间步长中,每个像元都会根据其具有的相邻单元数而更改状态:

  1. 0-1 邻居:单元格变为 0。
  2. 2 个邻居:单元格状态不会更改。
  3. 3 个邻居:单元格变为 1。
  4. 4+ 邻居:单元格变为 0。

该游戏是为无限网格而制定的。在这个电路中,我们将使用一个16x16的网格。为了让事情变得更有趣,我们将使用16x16的环形线圈,其中两侧环绕到网格的另一侧。例如,角单元格 (0,0) 有 8 个相邻单元:(15,1)、(15,0)、(15,15)、(0,1)、(0,15)、(1,1)、(1,0) 和 (1,15)。16x16 网格由长度为 256 的向量表示,其中每行 16 个单元格由一个子向量表示:q[15:0] 是第 0 行,q[31:16] 是第 1 行,依此类推(此工具接SystemVerilog,因此如果您愿意,可以使用 2D 向量。)
load:将数据加载到下一个时钟边沿的 q 中,用于加载初始状态。
问:游戏的 16x16 当前状态,每个时钟周期都会更新。
游戏状态应在每个时钟周期前进一个时间步长。

约翰·康威(John Conway)是数学家和生命游戏细胞自动机的创造者,于2020年4月11日因COVID-19去世。

四.题目分析

1.我们以一个4*4的二维单元网格举例,如图
4*4
出于更方便我们从左到右开始以此标定相应坐标,正好符合高位在右,低位在左。
由于是环形的所以对于(0,0)的邻居有(0,1),(0,3),(1,1),(1,0),(1,3),(3,1),(3,0),(3,3)。
可能这样看起来不是很明显我们将这个4x4二维单元格扩展成6x6的,如下图所示
6*6
其中绿色框起来的就是原来的4x4单元格。蓝色是扩展前原来格子序号,红色是扩展后的,坐标不动。
6*6_2
图中则可以看出(0,0)的八个邻居,分别是相邻4个和对角线4个。
若要实现16x16则可以将其扩展为18x18,这样就可以根据扩展后的位置关系确定其邻居的位置从而进行判断是否为1的数量,再进行下个状态的变化,然后输出即可。

五.具体步骤与解答

1.具体步骤

1) 先将16x16展开为18x18,定义一个reg 变量[323:0],将扩展后的18x18个元素存在里面,第一行和最后一行比较特殊需要单独写,其他行可采用for循环。
代码如下:

    //用于将18*18的矩阵铺展成r_data[323:0]中的18*18个元素 
    always @(*)begin
        r_data[17:0]   = {q[240],q[255:240],q[255]};    //第一行
        r_data[323:306]= {q[0],q[15:0],q[15]};          //第18行
        for (i=1;i<17;i=i+1) begin                      //1-17行可使用for循环进行表示 i记得自己定义
            r_data[i*18 +:18] = {q[(i-1)*16],q[(i-1)*16 +:16],q[i*16-1]};
        end                                                              
    end

注意:
+: 变量[起始地址 +:数据位宽]=变量[起始地址+数据位宽-1:起始地址]
-: 变量[结束地址 -:数据位宽]=变量[结束地址:结束地址-数据位宽+1]
2) 判断邻居为1的数目,首先定义一个integer cnt用于计数,8个邻居中若为1则加1,若为0则不变,对于每个元素都对其8个邻居进行判断。
代码如下:

    //用于判断当前元素周围的8个元素的值,1则累加,0则不变   (i,j)为坐标;
    always @(*)
        for(i=0;i<16;i=i+1) begin
            for(j=0;j<16;j=j+1) begin
                cnt = r_data[i*18+j]            //第一行
                    + r_data[i*18+j+1]
                    + r_data[i*18+j+2]
                    + r_data[(i+1)*18+j]        //第二行 因为本行包含被判断的元素本身需要排除,所以只有两个
                    + r_data[(i+1)*18+j+2]
                    + r_data[(i+2)*18+j]        //第三行
                    + r_data[(i+2)*18+j+1]
                    + r_data[(i+2)*18+j+2]

其中r_data[x],这个x为扩展为18x18后的序号。本always模块还未结束,故没有end。
3)得到了cnt,则下一步应该根据题目要求判断下一个状态的变化。
代码承接上一步如下:

                case(cnt)
                    0,1: q_next[i*16+j] = 0;
                    2  : q_next[i*16+j] = q[i*16+j];
                    3  : q_next[i*16+j] = 1;
                default: q_next[i*16+j] = 0;
                endcase

4)最后输出即可。
代码如下:

    //out
    always @(posedge clk) begin
            if(load) q<=data; //加载初始状态
            else     q<=q_next;
    end

2.完整代码

module top_module(
    input clk,
    input load,
    input [255:0] data,
    output [255:0] q ); 
    
    reg [323:0]  r_data;        //16*16扩展为18*18
    reg [323:0]  q_next;       //q的下一个状态

    integer i,j,cnt;
    
    //用于将18*18的矩阵铺展成r_data[323:0]中的18*18个元素 
    always @(*)begin
        r_data[17:0]   = {q[240],q[255:240],q[255]};    //第一行
        r_data[323:306]= {q[0],q[15:0],q[15]};          //第18行
        for (i=1;i<17;i=i+1) begin                      //1-17行可使用for循环进行表示
            r_data[i*18 +:18] = {q[(i-1)*16],q[(i-1)*16 +:16],q[i*16-1]}; // +: 变量[起始地址 +:数据位宽]=变量[起始地址+数据位宽-1:起始地址]
        end                                                               // -: 变量[结束地址 -:数据位宽]=变量[结束地址:结束地址-数据位宽+1]
    end
    
    //用于判断当前元素周围的8个元素的值,1则累加,0则不变   
    always @(*)
        for(i=0;i<16;i=i+1) begin
            for(j=0;j<16;j=j+1) begin
                cnt = r_data[i*18+j]             //第一行
                    + r_data[i*18+j+1]
                    + r_data[i*18+j+2]
                    + r_data[(i+1)*18+j]         //第二行 因为本行包含被判断的元素本身不需要排除,所以只有两个
                    + r_data[(i+1)*18+j+2]
                    + r_data[(i+2)*18+j]         //第三行
                    + r_data[(i+2)*18+j+1]
                    + r_data[(i+2)*18+j+2];
                
                //用于判断cnt(邻居为1的数量)从而决定q的下一个状态
                case(cnt)
                    0,1: q_next[i*16+j] = 0;
                    2  : q_next[i*16+j] = q[i*16+j];
                    3  : q_next[i*16+j] = 1;
                default: q_next[i*16+j] = 0;
                endcase
            end
    end
    
    //out
    always @(posedge clk) begin
            if(load) q<=data;
            else     q<=q_next;
    end
    
endmodule

六 . 总结

个人水平有限,尽可能细致讲解自己的思路,出现错误请指正。
本文思路方法参考于HDL bits讲解

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值