Skip to content

Latest commit

 

History

History
622 lines (558 loc) · 21.8 KB

game_of_life.org

File metadata and controls

622 lines (558 loc) · 21.8 KB

Introduction

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.

Architecture

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

LTCTOPRTC
LEFTRIGHT
LBCBOTTOMRBC

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

Design

VGA controller

// 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

Clock

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

Neighbours

//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

Matrix

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

Initial 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

Rules

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

Top

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

Constraints

#+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

Makefile

# 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