Files
Life_MiSTer/Life.sv
Dave King a6b3d96ac5 Add support for HighLife rules
Includes a script for converting RLE formatted patterns into mem
and some highlife patterns to demo.
2020-09-12 20:48:17 +01:00

220 lines
5.7 KiB
Systemverilog

/*============================================================================
* Conway's Game Of Life
* Copyright (C) 2020 Hrvoje Cavrak
*
* Please read LICENSE file.
*============================================================================*/
module emu
(
//Master input clock
input CLK_50M,
input RESET,
inout [45:0] HPS_BUS,
output CLK_VIDEO,
output CE_PIXEL,
output [7:0] VIDEO_ARX,
output [7:0] VIDEO_ARY,
output [7:0] VGA_R,
output [7:0] VGA_G,
output [7:0] VGA_B,
output reg VGA_HS,
output reg VGA_VS,
output VGA_DE, // = ~(VBlank | HBlank)
output VGA_F1,
output [1:0] VGA_SL,
output LED_USER, // 1 - ON, 0 - OFF.
output [1:0] LED_POWER,
output [1:0] LED_DISK,
input OSD_STATUS,
input HDMI_CLK
);
assign LED_USER = ioctl_download;
assign LED_DISK = 0;
assign LED_POWER = 0;
assign VGA_F1 = 0;
assign VIDEO_ARX = status[1] ? 8'd4 : 8'd16;
assign VIDEO_ARY = status[1] ? 8'd3 : 8'd9;
`include "build_id.v"
localparam CONF_STR =
{
"GameOfLife;;",
"-;",
"F1,MEM,Load board;",
"-;",
"O4,HighLife,Off,On;",
"O3,Running,Yes,No;",
"O2,Seed,Off,On;",
"O1,Aspect Ratio,16:9,4:3;",
"V,v0.1.",`BUILD_DATE
};
///////////////////////////////////////////////////
// HPS Connection
///////////////////////////////////////////////////
wire [31:0] status;
wire ioctl_download;
wire ioctl_wr;
wire [26:0] ioctl_addr;
wire [7:0] ioctl_dout;
hps_io #(.STRLEN($size(CONF_STR)>>3)) hps_io
(
.clk_sys(HDMI_CLK),
.HPS_BUS(HPS_BUS),
.conf_str(CONF_STR),
.status(status),
.ioctl_download(ioctl_download),
.ioctl_wr(ioctl_wr),
.ioctl_addr(ioctl_addr),
.ioctl_dout(ioctl_dout),
.ioctl_wait(ioctl_wr | (ioctl_wait))
);
///////////////////////////////////////////////////
// Game of life / video
///////////////////////////////////////////////////
reg output_pixel,
r1p1,
r1p2,
r2p1,
r2p2,
r3p1,
r3p2,
sync_wait;
wire pixel_out_row1,
pixel_out_row2,
pixel_out_fifo;
reg [6:0] repeat_cnt;
wire ioctl_wait = repeat_cnt > 0;
always @(posedge HDMI_CLK) begin
repeat_cnt <= repeat_cnt > 0 ? repeat_cnt - 1'b1 : 0;
if (ioctl_download & ioctl_wr & (~ioctl_wait))
repeat_cnt <= ioctl_dout[6:0];
sync_wait <= ioctl_download | (sync_wait & |{hc, vc});
end
/* If uploading new seed state, switch the shift register to 50 MHz HPS clock instead of the video clock.
Input feed is switched to data received.
*/
ring fb_shift_reg (
.clock(HDMI_CLK),
.enable(ioctl_download ? ioctl_wait | ioctl_wr : ~sync_wait),
.shiftin(ioctl_download ? ioctl_dout[7] : output_pixel),
.shiftout(pixel_out_fifo),
.status(status)
);
row row1 (
.clock(ioctl_download ? HDMI_CLK : conway_clk),
.shiftin(r2p1),
.shiftout(pixel_out_row1)
);
row row2 (
.clock(ioctl_download ? HDMI_CLK : conway_clk),
.shiftin(status[2] ? random_data[0] : r3p1), // status[2] => if set it feeds random pixels to next generation
.shiftout(pixel_out_row2)
);
/* Stop pixel shifting if we are downloading the new initial state or waiting for new frame to start */
wire conway_clk = HDMI_CLK & (~ioctl_download) & (~sync_wait);
wire [3:0] neighbor_count = r1p1 + r1p2 + pixel_out_row1 + r2p1 + pixel_out_row2 + r3p1 + r3p2 + pixel_out_fifo;
/* One large shift register and two row-sized ones to enable counting all pixel's neighbors in one clock */
always @(posedge conway_clk) begin
/* Row shift registers are a little shorter and padded with two registers each so individual pixels can be
accessed and cell neighbor count determined.
*/
r1p1 <= r1p2; r1p2 <= pixel_out_row1;
r2p1 <= r2p2; r2p2 <= pixel_out_row2;
r3p1 <= r3p2; r3p2 <= pixel_out_fifo;
/* status[3] = running flag. If true (default false), the existing pixel is simply copied to the next generation.
status[4] = HighLife (B36/S23) flag - https://www.conwaylife.com/wiki/OCA:HighLife
*/
if(status[4])
output_pixel <= status[3] ? r2p2 : (neighbor_count | r2p2) == 4'd3) || ((neighbor_count ^ r2p2) == 4'd6;
else
output_pixel <= status[3] ? r2p2 : (neighbor_count | r2p2) == 4'd3;
/* Monochrome output, if pixel is set we set the brightness to max, if not set it to min */
fb_pixel <= {8{output_pixel}};
end
////////////////////////////////////////////////////////////////////
// Video //
////////////////////////////////////////////////////////////////////
assign CLK_VIDEO = HDMI_CLK;
assign CE_PIXEL = 1'b1;
reg [11:0] hc, vc; // Horizontal and vertical counters
reg [7:0] fb_pixel;
assign VGA_G = fb_pixel;
assign VGA_R = fb_pixel;
assign VGA_B = fb_pixel;
assign VGA_DE = (hc < 12'd1920 && vc < 12'd1080);
wire [30:0] random_data;
/* Enables pseudo-random seeding of initial state */
random lfsr(
.clock(HDMI_CLK),
.lfsr(random_data)
);
/* Video timings explained: https://timetoexplore.net/blog/video-timings-vga-720p-1080p */
always @(posedge HDMI_CLK) begin
hc <= hc + 1'd1;
if(hc == 12'd2199) begin // End of line reached
hc <= 12'd0;
vc <= (vc == 12'd1124) ? 12'b0 : vc + 1'd1; // End of frame reached
end
if(hc == 12'd2007) VGA_HS <= 1'b1; // Horizontal sync pulse
if(hc == 12'd2051) VGA_HS <= 1'b0;
if(vc == 12'd1084) VGA_VS <= 1'b1; // Vertical sync pulse
if(vc == 12'd1089) VGA_VS <= 1'b0;
end
endmodule