The game of life is a no player game invented by British mathematician John Conway. It consists of an array of cells that can be in one of two states: dead or alive. The next state of a particular cells depends on the state of the surrounding cells. More specifically, it follows the following rules:
- Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overpopulation.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
We are going to implement the Game of Life in verilog. We are going to use only free software tools for all development, including synthesis and place and route. We have a 640x480 screen. Initially, the cell is going to be 32x32 pixels, but in the future we will try to make the cell as small as possible. Therefore, the screen is going to look like this: RBC = Right Bottom Corner LBC = Right Bottom Corner RTC = Right Bottom Corner LTC = Right Bottom Corner
LTC | TOP | RTC | |||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
LEFT | RIGHT | ||||||||||||||||||
LBC | BOTTOM | RBC |
In terms of modules, we are going to need the following:
- VGA controller: to print in the screen the content of the matrix
- 25MHz clock generator: to drive the pixel clock
- Matrix: hold the values of the current and the next matrix
- Neighbours: for a given cell, calculate the position of all 8 neighbours. This is needed because the neighbours in edge and corner cases
- Rules: decides what is the next state of a particular cell is
- Top: wires all the previous modules together
With the neighbours we have to do several things.
- For a certain x and y, find where they are in the matrix
- Once all neighbours are located, get the state for all of them.
- Sum the value of all neighbours
- Decide what to do in the next state for that particular cell
// Copyright (c) 2017-2018 Roland Coeurjoly
// This program is GPL Licensed. See LICENSE for the full license.
module vga_controller(
input wire i_clk_25MHz,
input wire [2:0] i_rgb,
output wire o_hsync, //horizontal sync out
output wire o_vsync, //vertical sync out
output reg o_red, //o_red vga output
output reg o_green, //o_green vga output
output reg o_blue, //o_blue vga output
output reg [9:0] o_x,
output reg [9:0] o_y
);
/**/
// video structure constants
parameter ACTIVE_H_VIDEO = 640;
parameter ACTIVE_V_VIDEO = 480;
parameter HFP = 16; // horizontal front porch length
parameter H_PULSE = 96; // hsync pulse length
parameter HBP = 48; // horizontal back porch length
parameter VFP = 10; // vertical front porch length
parameter V_PULSE = 2; // vsync pulse length
parameter VBP = 33; // vertical back porch length
parameter BLACK_H = HFP + H_PULSE + HBP;
parameter BLACK_V = VFP + V_PULSE + VBP;
parameter H_PIXELS = BLACK_H + ACTIVE_H_VIDEO;
parameter V_LINES = BLACK_V + ACTIVE_V_VIDEO;
// active horizontal video is therefore: 784 - 144 = 640
// active vertical video is therefore: 515 - 35 = 480
// registers for storing the horizontal & vertical counters
reg [9:0] h_counter;
reg [9:0] v_counter;
initial begin
o_x = 0;
o_y = 0;
o_red = 0;
o_green = 0;
o_blue = 0;
h_counter = 0;
v_counter = 0;
end
always @(posedge i_clk_25MHz)
begin
// keep counting until the end of the line
if (h_counter < H_PIXELS - 1)
h_counter <= h_counter + 1;
else
// When we hit the end of the line, reset the horizontal
// counter and increment the vertical counter.
// If vertical counter is at the end of the frame, then
// reset that one too.
begin
h_counter <= 0;
if (v_counter < V_LINES - 1)
v_counter <= v_counter + 1;
else
v_counter <= 0;
end // else: !if(h_counters < H_PIXELS - 1)
end // always @ (posedge i_clk_25MHz)
// generate sync pulses (active low)
// ----------------
// "assign" statements are a quick way to
// give values to variables of type: wire
wire active_video;
assign o_hsync = (h_counter >= HFP && h_counter < HFP + H_PULSE) ? 0:1;
assign o_vsync = (v_counter >= VFP && v_counter < VFP + V_PULSE) ? 0:1;
assign active_video = (h_counter >= HFP + H_PULSE + HBP && v_counter >= VFP + V_PULSE + VBP) ? 1:0;
// displao_y 100% saturation colorbars
// ------------------------
// Combinational "always block", which is a block that is
// triggered when anything in the "sensitivity list" changes.
// The asterisk implies that everything that is capable of triggering the block
// is automatically included in the sensitivty list. In this case, it would be
// equivalent to the following: always @(hc, vc)
// Assignment statements can only be used on type "reg" and should be of the "blocking" type: =
always @(posedge i_clk_25MHz) begin
if (active_video == 1) begin
o_red <= i_rgb[2];
o_green <= i_rgb[1];
o_blue <= i_rgb[0];
o_x <= h_counter - HFP - H_PULSE - HBP;
o_y <= v_counter - VFP - V_PULSE - VBP;
end // if (active_video == 1)
else begin
o_red <= 0;
o_green <= 0;
o_blue <= 0;
o_x <= 0;
o_y <= 0;
end // else: !if(active_video == 1)
end // always @ (posedge i_clk_25MHz)
endmodule
This is the clock. We get the values of the PLL generator by running the following code:
icepll -i 12 -o 25
// Copyright (c) 2017-2018 Roland Coeurjoly
// This program is GPL Licensed. See LICENSE for the full license.
module clk_25MHz_generator(
input wire i_clk_12MHz,
output wire o_clk_25MHz
);
`ifndef SYNTHESIS
// SIMULATION
assign o_clk_25MHz = i_clk_12MHz;
`else
// SYNTHESIS
SB_PLL40_CORE #(
.FEEDBACK_PATH("SIMPLE"),
.PLLOUT_SELECT("GENCLK"),
.DIVR(4'b0001),
.DIVF(7'b1000010),
.DIVQ(3'b100),
.FILTER_RANGE(3'b001),
) uut (
.REFERENCECLK(i_clk_12MHz),
.PLLOUTCORE(o_clk_25MHz),
.RESETB(1'b1),
.BYPASS(1'b0)
);
`endif
endmodule
//Where are the neighbours?
module neighbours(
input wire [9:0] i_x,
input wire [9:0] i_y,
output reg [3:0] o_sum
);
parameter MAX_i = 14;
parameter MAX_j = 19;
always @(*) begin
//limit cases
if ((i_y == 0) || (i_y == MAX_j) || (i_x == 0) || (i_x == MAX_i))
begin
//upper border cases
if (i_y == 0)
begin
//upper left corner case
if (i_x == 0)
begin
upper_cell <= {MAX_j,i_x};
upper_right_cell <= {MAX_j,i_x + 1};
right_cell <= {i_y,i_x + 1};
lower_right_cell <= {i_y + 1,i_x + 1};
lower_cell <= {i_y + 1,i_x};
lower_left_cell <= {i_y + 1,MAX_i};
left_cell <= {i_y,MAX_i};
upper_left_cell <= {MAX_j,MAX_i};
end // if (i_x == 0)
//upper right corner case
else if (i_x == MAX_i)
begin
upper_cell <= {MAX_j,i_x};
upper_right_cell <= {MAX_j,i_x + 1};
right_cell <= {i_y,i_x + 1};
lower_right_cell <= {i_y + 1,i_x + 1};
lower_cell <= {i_y + 1,i_x};
lower_left_cell <= {i_y + 1,MAX_i};
left_cell <= {i_y,MAX_i};
upper_left_cell <= {MAX_j,MAX_i};
end // if (i_x == MAX_i)
// regular upper border case
else
begin
upper_cell <= {MAX_j,i_x};
upper_right_cell <= {MAX_j,i_x + 1};
right_cell <= {i_y,i_x + 1};
lower_right_cell <= {i_y + 1,i_x + 1};
lower_cell <= {i_y + 1,i_x};
lower_left_cell <= {i_y + 1,i_x - 1};
left_cell <= {i_y,i_x - 1};
upper_left_cell <= {MAX_j,i_x - 1};
end // else: !if(i_x == MAX_i)
end // if (i_y == 0)
//right border cases
else if (i_x == MAX_i)
begin
//lower right corner case
if (i_y == MAX_j)
begin
upper_cell <= {i_y - 1,i_x};
upper_right_cell <= {i_y + 1,0};
right_cell <= {i_y,0};
lower_right_cell <= {0,0};
lower_cell <= {0,MAX_i};
lower_left_cell <= {0,i_x - 1};
left_cell <= {i_y,MAX_i};
upper_left_cell <= {i_y,MAX_i};
end // if (i_y == MAX_j)
//regular right border case
else //if (i_y != 0)
begin
upper_cell <= {i_y - 1,i_x};
upper_right_cell <= {i_y - 1,0};
right_cell <= {i_y,0};
lower_right_cell <= {i_y + 1,0};
lower_cell <= {i_y + 1,i_x};
lower_left_cell <= {i_y + 1,i_x - 1};
left_cell <= {i_y,i_x - 1};
upper_left_cell <= {i_y - 1,i_x - 1};
end // if (i_y != 0)
end // if (i_x == MAX_i)
//lower border cases
else if (i_y == MAX_j)
begin
//lower left corner case
if (i_x == 0)
begin
upper_cell <= {i_y - 1,i_x};
upper_right_cell <= {i_y + 1,i_x + 1};
right_cell <= {i_y,i_x + 1};
lower_right_cell <= {i_y,i_x + 1};
lower_cell <= {0,i_x};
lower_left_cell <= {0,i_x - 1};
left_cell <= {i_y,MAX_i};
upper_left_cell <= {i_y - 1,MAX_i};
end // if (i_x == 0)
//regular lower border case
else //if (i_x != MAX_i)
begin
upper_cell <= {i_y - 1,i_x};
upper_right_cell <= {i_y - 1,i_x + 1};
right_cell <= {i_y,i_x + 1};
lower_right_cell <= {0,i_x + 1};
lower_cell <= {0,i_x};
lower_left_cell <= {0,i_x - 1};
left_cell <= {i_y,i_x - 1};
upper_left_cell <= {i_y - 1,i_x - 1};
end // if (i_x != MAX_i)
end // if (i_y == MAX_j)
//regular left border case
else //if (i_x == 0)
begin
upper_cell <= {i_y - 1,i_x};
upper_right_cell <= {i_y - 1,i_x + 1};
right_cell <= {i_y,i_x + 1};
lower_right_cell <= {i_y + 1,i_x + 1};
lower_cell <= {i_y + 1,i_x};
lower_left_cell <= {i_y + 1,MAX_i};
left_cell <= {i_y,MAX_i};
upper_left_cell <= {i_y - 1,MAX_i};
end // if (i_x == 0)
end // if ((i_y == 0) or (i_y == MAX_j) or (i_x == 0) or (i_x == MAX_i))
//normal inner surface
else
begin
upper_cell <= {i_y - 1,i_x};
upper_right_cell <= {i_y - 1,i_x + 1};
right_cell <= {i_y,i_x + 1};
lower_right_cell <= {i_y + 1,i_x + 1};
lower_cell <= {i_y + 1,i_x};
lower_left_cell <= {i_y + 1,i_x - 1};
left_cell <= {i_y,i_x - 1};
upper_left_cell <= {i_y - 1,i_x - 1};
end // else: !if((i_y == 0) or (i_y == MAX_j) or (i_x == 0) or (i_x == MAX_i))
end // always @ (i_x)
wire [3:0] sum_neighbours;
assign sum_neighbours = upper_cell + upper_right_cell + right_cell + lower_right_cell + lower_cell + lower_left_cell + left_cell + upper_left_cell;
endmodule
module matrix(
input wire [9:0] i_x,
input wire [9:0] i_y,
output reg [2:0] o_rgb
);
// The screen is 640 x 480 pixels
// We divide the screen in sprites of 32 x 32
// We get a screen composed of 20 x 15 sprites of 32 x 32 pixels each
parameter MAX_i = 14;
parameter MAX_j = 19;
parameter [2:0] BLACK = 3'b000;
parameter [2:0] BLUE = 3'b001;
parameter [2:0] GREEN = 3'b010;
parameter [2:0] CYAN = 3'b011;
parameter [2:0] RED = 3'b100;
parameter [2:0] MAGENTA = 3'b101;
parameter [2:0] YELLOW = 3'b110;
parameter [2:0] WHITE = 3'b111;
reg [MAX_j : 0] screen [0 : MAX_i];
reg [MAX_j : 0] next_screen [0 : MAX_i];
reg [4:0] i, j;
wire [4:0] sprite_x = i_x[9:5];
wire [3:0] sprite_y = i_y[8:5];
wire [4:0] index_x = i_x[4:0];
wire [4:0] index_y = i_y[4:0];
wire [19:0] position = {i_x, i_y};
initial begin
$readmemb("assets/initial_matrix.txt", screen);
end
always @(*) begin
if (screen[sprite_y][sprite_x] == 1)
o_rgb = BLACK;
else
o_rgb = WHITE;
end // always @ (*)
endmodule // matrix
For convenience, we read a file for the initial state of cells
00000000000000000000
00000000000000000000
00000000000000010000
00000000000000100000
00000011111110000000
00000000010000010000
00000000010000100000
00000011111110000000
00000000010000000000
00000000010000100000
00000011111110010000
00000000000000000000
00000000000000000000
00000000000000000000
00000000000000000000
echo "obase=2;243" | bc
module rules(
input wire [4:0] sum_neighbours,
input wire [9:0] i_x,
input wire [0:MAX_i] i_screen [0:MAX_j],
input wire [9:0] i_y,
output reg [2:0] rgb
);
parameter MAX_j = 14;
parameter MAX_i = 19;
reg [0:MAX_i] screen [0:MAX_j];
reg [0:MAX_i] next_screen [0:MAX_j];
reg [9:0] i;
reg [9:0] j;
wire [4:0] x = i_x[9:5];
wire [3:0] y = i_y[8:5];
//Rules of Conways's Game of Life
always @(*) begin
case (sum_neighbours)
//Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
4'b0000, 4'b0001: begin
//if the cell is alive
if (screen[y][x] == 1) next_screen[y][x] = 0;
end
//Any live cell with two or three live neighbours lives on to the next generation.
4'b0010, 4'b0011: begin
if (screen[y][x] == 1) next_screen[y][x] = 1;
end
//Any live cell with more than three live neighbours dies, as if by overpopulation.
default: next_screen[y][x] = 0;
endcase
end // if (screen[y][x] == 1)
//if the cell is dead
else
begin
case (sum_neighbours)
//Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
4'b0011: next_screen[y][x] = 1;
default: next_screen[y][x] = 0;
endcase // case (sum_neighbours)
end // if (screen[y][x] == 0)
end
endmodule
module game_of_life_top(
input wire i_clk_12MHz,
output wire o_hsync, //horizontal sync out
output wire o_vsync, //vertical sync out
output wire o_red, //red vga output
output wire o_green, //green vga output
output wire o_blue //blue vga output
);
wire clk_25MHz;
wire [2:0] rgb;
wire [9:0] x, y;
clk_25MHz_generator clk_36MHz_generator1(
.i_clk_12MHz(i_clk_12MHz),
.o_clk_25MHz(clk_25MHz)
);
vga_controller vga_controller1(
.i_clk_25MHz(clk_25MHz),
.i_rgb(rgb),
.o_hsync(o_hsync),
.o_vsync(o_vsync),
.o_red(o_red),
.o_green(o_green),
.o_blue(o_blue),
.o_x(x),
.o_y(y)
);
matrix matrix1(
.i_x(x),
.i_y(y),
.o_rgb(rgb)
);
neighbours neighbours1(
.i_x(x),
.i_y(y),
);
endmodule
#+NAME constraints
set_io i_clk_12MHz 21
set_io o_hsync 113
set_io o_vsync 112
set_io o_red 119
set_io o_green 118
set_io o_blue 117
# call with make MODULE=moduleName sim|svg|upload
TOP:=game_of_life_top
PROJECT_PATH:=~/Game-of-Life/
FORMAL_PATH:=$(PROJECT_PATH)formal/
RTL_PATH:=$(PROJECT_PATH)rtl/
SIM_PATH:=$(PROJECT_PATH)sim/
SYNTH_PATH:=$(PROJECT_PATH)syn/
ifndef $(MODULE)
MODULE=$(TOP)
endif
ifeq ($(MODULE), $(TOP))
DEPS:=\
$(RTL_PATH)vga_controller.v \
$(RTL_PATH)clk_25MHz_generator.v \
$(RTL_PATH)matrix.v
FORMAL:=\
ship.v \
bullet.v
# AUXFILES:=\
# const.vh
# YOSYSOPT:=-retime -abc2
endif
ifndef $(MEMORY)
MEMORY="1k"
endif
all: bin
bin: $(MODULE).bin
sim: $(MODULE)_tb.vcd
json: $(MODULE).json
svg: assets/$(MODULE).svg
$(MODULE)_tb.vcd: $(RTL_PATH)$(MODULE).v $(DEPS) $(SIM_PATH)$(MODULE)_tb.v $(AUXFILES)
iverilog $^ -o $(MODULE)_tb.out
./$(MODULE)_tb.out
gtkwave $@ $(MODULE)_tb.gtkw &
$(MODULE).bin: $(SYNTH_PATH)$(MODULE).pcf $(RTL_PATH)$(MODULE).v $(DEPS) $(AUXFILES)
yosys -p "synth_ice40 -blif $(MODULE).blif $(YOSYSOPT)" -l $(MODULE).log -q $(RTL_PATH)$(MODULE).v $(DEPS)
arachne-pnr -d $(MEMORY) -p $(SYNTH_PATH)$(MODULE).pcf $(MODULE).blif -o $(MODULE).pnr
icepack $(MODULE).pnr $(MODULE).bin
$(MODULE).json: $(MODULE).v $(DEPS)
yosys -p "prep -top $(MODULE); write_json $(MODULE).json" (MODULE).v $(DEPS)
assets/$(MODULE).svg: $(MODULE).json
netlistsvg $(MODULE).json -o assets/$(MODULE).svg
upload: $(MODULE).bin
iceprog $(MODULE).bin
clean:
rm -f *.bin *.pnr *.blif *.out *.vcd *~
verify_bullet:
sby -f $(FORMAL_PATH)bullet.sby
verify_clk_25MHz_generator:
sby -f $(FORMAL_PATH)clk_25MHz_generator.sby
verify_edge_detector_debouncer:
sby -f $(FORMAL_PATH)edge_detector_debouncer.sby
verify_gameplay:
sby -f $(FORMAL_PATH)gameplay.sby
verify_invaders:
sby -f $(FORMAL_PATH)invaders.sby
verify_player:
sby -f $(FORMAL_PATH)player.sby
verify_ship:
sby -f $(FORMAL_PATH)ship.sby
verify_space_invaders_top:
sby -f $(FORMAL_PATH)space_invaders_top.sby
verify_sprite_drawer:
sby -f $(FORMAL_PATH)sprite_drawer.sby
verify_timer_1us:
sby -f $(FORMAL_PATH)timer_1us.sby
verify_tone_generator:
sby -f $(FORMAL_PATH)tone_generator.sby
verify_vga_controller:
sby -f $(FORMAL_PATH)vga_controller.sby
.PHONY: all clean json svg sim