neorv32/sw/example/game_of_life/main.c

380 lines
12 KiB
C
Raw Permalink Normal View History

2024-02-24 08:25:27 +00:00
// #################################################################################################
// # << NEORV32 - Conway's Game of Life >> #
// # ********************************************************************************************* #
// # BSD 3-Clause License #
// # #
// # Copyright (c) 2023, Stephan Nolting. All rights reserved. #
// # #
// # Redistribution and use in source and binary forms, with or without modification, are #
// # permitted provided that the following conditions are met: #
// # #
// # 1. Redistributions of source code must retain the above copyright notice, this list of #
// # conditions and the following disclaimer. #
// # #
// # 2. Redistributions in binary form must reproduce the above copyright notice, this list of #
// # conditions and the following disclaimer in the documentation and/or other materials #
// # provided with the distribution. #
// # #
// # 3. Neither the name of the copyright holder nor the names of its contributors may be used to #
// # endorse or promote products derived from this software without specific prior written #
// # permission. #
// # #
// # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS #
// # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF #
// # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE #
// # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, #
// # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE #
// # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED #
// # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING #
// # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED #
// # OF THE POSSIBILITY OF SUCH DAMAGE. #
// # ********************************************************************************************* #
// # The NEORV32 Processor - https://github.com/stnolting/neorv32 (c) Stephan Nolting #
// #################################################################################################
/**********************************************************************//**
* @file game_of_life/main.c
* @author Stephan Nolting
* @brief Conway's game of life in a UART terminal.
**************************************************************************/
#include <neorv32.h>
/**********************************************************************//**
* @name User configuration
**************************************************************************/
/**@{*/
/** UART BAUD rate */
#define BAUD_RATE 19200
/** Universe x size (has to be a multiple of 8) */
#define NUM_CELLS_X 160
/** Universe y size */
#define NUM_CELLS_Y 40
/** Delay between generations in ms */
#define GEN_DELAY 500
/** Symbol for dead cell */
#define CELL_DEAD (' ')
/** Symbol for alive cell */
#define CELL_ALIVE ('#')
/**@}*/
/**********************************************************************//**
* The universe
**************************************************************************/
uint8_t universe[2][NUM_CELLS_X/8][NUM_CELLS_Y];
// Prototypes
void clear_universe(int u);
void set_cell(int u, int x, int y);
int get_cell(int u, int x, int y);
int get_neighborhood(int u, int x, int y);
void print_universe(int u);
int pop_count(int u);
uint32_t xorshift32(void);
/**********************************************************************//**
* Conway's Game of Life.
*
* @note This program requires the UART to be synthesized (the TRNG is optional).
*
* @return 0 if execution was successful
**************************************************************************/
int main(void) {
// check if UART unit is implemented at all
if (neorv32_uart0_available() == 0) {
return 1;
}
// capture all exceptions and give debug info via UART
// this is not required, but keeps us safe
neorv32_rte_setup();
// setup UART at default baud rate, no interrupts
neorv32_uart0_setup(BAUD_RATE, 0);
while (1) {
int u = 0, cell = 0, n = 0;
int x, y;
int trng_available = 0;
uint8_t trng_data;
// initialize universe
uint32_t generation = 0;
clear_universe(0);
clear_universe(1);
// intro
neorv32_uart0_printf("\n\n<<< Conways's Game of Life >>>\n\n");
neorv32_uart0_printf("This program requires a terminal resolution of at least %ux%u characters.\n", NUM_CELLS_X+2, NUM_CELLS_Y+3);
neorv32_uart0_printf("Press any key to start a random-initialized torus-style universe of %ux%u cells.\n", NUM_CELLS_X, NUM_CELLS_Y);
neorv32_uart0_printf("You can pause/restart the simulation by pressing any key.\n");
// check if TRNG was synthesized
if (neorv32_trng_available()) {
neorv32_uart0_printf("\nTRNG detected. Using TRNG for universe initialization.\n");
neorv32_trng_enable(0);
trng_available = 1;
}
// randomize until key pressed
while (neorv32_uart0_char_received() == 0) {
xorshift32();
}
neorv32_uart0_char_received_get(); // discard received char
// initialize universe using random data
for (x=0; x<NUM_CELLS_X/8; x++) {
for (y=0; y<NUM_CELLS_Y; y++) {
if (trng_available) {
while (1) {
if (neorv32_trng_get(&trng_data)) {
continue;
}
else {
break;
}
}
universe[0][x][y] = trng_data; // use data from TRNG
}
else {
universe[0][x][y] = (uint8_t)xorshift32(); // use data from PRNG
}
}
}
while(1) {
// user abort?
if (neorv32_uart0_char_received()) {
neorv32_uart0_char_received_get(); // discard received char
neorv32_uart0_printf("\nRestart (y/n)?");
if (neorv32_uart0_getc() == 'y') {
break;
}
}
// print generation, population count and the current universe
neorv32_uart0_printf("\n\nGeneration %u: %u/%u living cells\n", (uint32_t)generation, (uint32_t)pop_count(u), NUM_CELLS_X*NUM_CELLS_Y);
print_universe(u);
// compute next generation
clear_universe((u + 1) & 1);
for (x=0; x<NUM_CELLS_X; x++) {
for (y=0; y<NUM_CELLS_Y; y++) {
cell = get_cell(u, x, y); // state of current cell
n = get_neighborhood(u, x, y); // number of living neighbor cells
// -- classic rule set --
// if center cell is dead -> cell comes to life when there are exactly 3 living cells around
// if center cell is alive -> stay alive if there are 2 or three living cells around
// else -> cell is/becomes dead
if (((cell == 0) && (n == 3)) || ((cell != 0) && ((n == 2) || (n == 3)))) {
set_cell((u + 1) & 1, x, y);
}
} // y
} // x
u = (u + 1) & 1; // switch universe
generation++;
// wait GEN_DELAY ms
neorv32_cpu_delay_ms(GEN_DELAY);
}
}
return 0;
}
/**********************************************************************//**
* Print universe via UARt.
*
* @param[in] u Universe select (0 or 1).
**************************************************************************/
void print_universe(int u){
int16_t x, y;
neorv32_uart0_putc('+');
for (x=0; x<NUM_CELLS_X; x++) {
neorv32_uart0_putc('-');
}
neorv32_uart0_putc('+');
neorv32_uart0_putc('\r');
neorv32_uart0_putc('\n');
for (y=0; y<NUM_CELLS_Y; y++) {
neorv32_uart0_putc('|');
for (x=0; x<NUM_CELLS_X; x++) {
if (get_cell(u, x, y))
neorv32_uart0_putc((char)CELL_ALIVE);
else
neorv32_uart0_putc((char)CELL_DEAD);
}
// end of line
neorv32_uart0_putc('|');
neorv32_uart0_putc('\r');
neorv32_uart0_putc('\n');
}
neorv32_uart0_putc('+');
for (x=0; x<NUM_CELLS_X; x++) {
neorv32_uart0_putc('-');
}
neorv32_uart0_putc('+');
}
/**********************************************************************//**
* Kill all cells in universe.
*
* @param[in] u Universe select (0 or 1).
**************************************************************************/
void clear_universe(int u){
uint16_t x, y;
for (x=0; x<NUM_CELLS_X/8; x++) {
for (y=0; y<NUM_CELLS_Y; y++) {
universe[u][x][y] = 0;
}
}
}
/**********************************************************************//**
* Make cell alive.
*
* @param[in] u Universe select (0 or 1).
* @param[in] x X coordinate of cell.
* @param[in] y Y coordinate of cell.
**************************************************************************/
void set_cell(int u, int x, int y){
if ((x >= NUM_CELLS_X) || (y >= NUM_CELLS_Y))
return; // out of range
universe[u][x>>3][y] |= (uint8_t)(1 << (7 - (x & 7)));
}
/**********************************************************************//**
* Get state of cell.
*
* @param[in] u Universe select (0 or 1).
* @param[in] x X coordinate of cell.
* @param[in] y Y coordinate of cell.
* @return Cell is dead when 0, cell is alive when 1.
**************************************************************************/
int get_cell(int u, int x, int y){
// range check: wrap around -> torus-style universe
if (x < 0)
x = NUM_CELLS_X-1;
if (x > NUM_CELLS_X-1)
x = 0;
if (y < 0)
y = NUM_CELLS_Y-1;
if (y > NUM_CELLS_Y-1)
y = 0;
// check bit according to cell
uint8_t tmp = universe[u][x>>3][y];
tmp &= 1 << (7 - (x & 7));
if (tmp == 0)
return 0; // DEAD
else
return 1; // ALIVE
}
/**********************************************************************//**
* Get number of living cells in neighborhood.
*
* @param[in] u Universe select (0 or 1).
* @param[in] x X coordinate of the neighborhood's center cell.
* @param[in] y Y coordinate of the neighborhood's center cell.
* @return Number of living cells in neighborhood (0..9).
**************************************************************************/
int get_neighborhood(int u, int x, int y){
// Cell index layout:
// 012
// 3#4
// 567
int num = 0;
num += get_cell(u, x-1, y-1); // 0
num += get_cell(u, x, y-1); // 1
num += get_cell(u, x+1, y-1); // 2
num += get_cell(u, x-1, y); // 3
num += get_cell(u, x+1, y); // 4
num += get_cell(u, x-1, y+1); // 5
num += get_cell(u, x, y+1); // 6
num += get_cell(u, x+1, y+1); // 7
return num;
}
/**********************************************************************//**
* Count living cells in universe.
*
* @param[in] u Universe select (0 or 1).
* @return Number of living cells.
**************************************************************************/
int pop_count(int u) {
int x, y, cnt;
cnt = 0;
for (x=0; x<NUM_CELLS_X; x++) {
for (y=0; y<NUM_CELLS_Y; y++) {
cnt += (int)get_cell(u, x, y);
}
}
return cnt;
}
/**********************************************************************//**
* Simple pseudo random number generator.
*
* @return Random number.
**************************************************************************/
uint32_t xorshift32(void) {
static uint32_t x32 = 314159265;
x32 ^= x32 << 13;
x32 ^= x32 >> 17;
x32 ^= x32 << 5;
return x32;
}