Rule 90

.数组的异或规则是对应分量两两异或,得到一个新的数组。

Solution

module top_module(
    input clk,
    input load,
    input [511:0] data,
    output [511:0] q ); 

    always @(posedge clk) begin
        if (load)  //别忘了先加载  
            q <= data;
        else  
            q <= {1'b0,q[511:1]} ^ {q[510:0],1'b0};
    end
endmodule

Rule 110

将左、中、右状态组合为 3 位索引,查表(Rule 110 的转移表)得到下一状态。

将左、中、右分别作为三个变量(含有512个分量的数组),作卡诺图:

L\C R00011110
00111
10101

于是,$C^{(n+1)} = \bar{C}R +\bar{L}C+C\bar{R}$

Solution

module top_module(
    input clk,
    input load,
    input [511:0] data,
    output [511:0] q
); 
    
    wire [511:0] L,C,R;
    assign L = {1'b0,q[511:1]};
    assign C = q;
    assign R = {q[510:0],1'b0};
    always @(posedge clk) begin 
        if(load)
            q <= data;
        else
            q <= (~C & R) | (~L & C) | (C & ~R);
    end
endmodule

Conway's Game of Life 16x16

题意

生命游戏是二维元胞自动机,每个细胞(cell)状态为1(活)或0(死),下一状态由8 个邻居的存活数决定:

邻居存活数细胞下一状态
0-1 个死亡(变 0)
2 个保持当前状态
3 个复活(变 1,即使之前是死的)
≥4 个死亡(变 0)

索引转换

由一维索引转换到二维索引:

  • 行号 i

    • idx 除以 16取整(舍去余数);
    • 等效于: idx >> 4(右移 4 位 = 除以 2⁴)。
  • 列号 j

    • j = idx % 16(取模运算,即余数);
    • 等效于按位与idx & 15(15 的二进制是 1111,按位与之后达到保留低 4 位(即余数)的效果)。

由二维索引转换到一维索引:
(i, j) 的一维索引为 i * 16 + j.

右移位运算符

>> 是 右移位运算符

  1. 逻辑右移(>>
    将二进制数向右移动,左侧空出的位用 0 填充,不考虑符号位
    语法:A >> N(将 A 右移 N 位)。
  2. 算术右移(>>>
    将二进制数向右移动,左侧空出的位用符号位填充(即保持负数的符号)。
    语法:A >>> N(将 A 右移 N 位)。

Solution

module top_module(
    input        clk,
    input        load,
    input  [255:0] data,
    output [255:0] q
);

reg [255:0] q_reg;  // 模块级寄存器存储当前状态
assign q = q_reg;    // 输出直接映射到寄存器

always @(posedge clk) begin

    // ------- 步骤1:声明过程块内的变量(必须放在块的开头)---------
    
    reg [255:0] next_q;    //存储下一状态(局部reg)
    integer idx;           //当前细胞的一维索引 
    integer dr;            //相对于当前细胞的行偏移(Delta Row)
    integer dc;            //相对于当前细胞的列偏移(Delta Column)
    integer i;             //当前细胞的二维行索引
    integer j;             //当前细胞的二维列索引
    integer r;             //邻居的绝对坐标的行索引
    integer c;             //邻居的绝对坐标的列索引
    integer neighbor_idx;  //邻居的一维索引
    integer cnt;           //邻居的存活数目
    
    if (load) begin
        // 加载模式:直接赋值
        q_reg <= data;
    end else begin
        
        // --------- 步骤2:遍历每个细胞,计算下一状态 ------------
        
        for (idx = 0; idx < 256; idx = idx + 1) begin
            i = idx >> 4;       // 计算当前细胞的行号(0~15)
            j = idx & 15;       // 计算当前细胞的列号(0~15)
            cnt = 0;            // 邻居存活数初始化
            
            // 遍历8个邻居(dr, dc ∈ {-1, 0, 1},左右位移只能取1或-1,所以待会需要跳过自身)
            for (dr = -1; dr <= 1; dr = dr + 1) begin
                for (dc = -1; dc <= 1; dc = dc + 1) begin
                    if (dr == 0 && dc == 0) continue;  // 跳过当前细胞
                    
                    // 处理环面边界(确保行/列在0~15范围内)
                    r = (i + dr + 16) % 16;  // +16避免负数,再取模
                    c = (j + dc + 16) % 16;
                    neighbor_idx = r * 16 + c;  // 邻居的一维索引
                    
                    // 统计存活邻居
                    if (q_reg[neighbor_idx]) begin
                        cnt = cnt + 1;
                    end
                end
            end
            
            // --------- 步骤3:根据规则更新下一状态 -------------
            
            if (cnt < 2 || cnt >= 4) begin
                next_q[idx] = 1'b0;
            end else if (cnt == 2) begin
                next_q[idx] = q_reg[idx];  // 保持当前状态
            end else begin  // cnt == 3
                next_q[idx] = 1'b1;        // 复活
            end
        end
        
        // 统一更新状态(时序安全,避免竞争)
        q_reg <= next_q;
    end
end

endmodule

本题是硬件实现元胞自动机的经典方法!