-
Notifications
You must be signed in to change notification settings - Fork 0
/
Life.sv
213 lines (156 loc) · 5.42 KB
/
Life.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/*============================================================================
* 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;",
"-;",
"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 false, the existing pixel is simply copied to the next generation. */
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