diff options
-rw-r--r-- | README.md | 60 | ||||
-rwxr-xr-x | salis | 58 | ||||
-rw-r--r-- | sim.png | bin | 0 -> 311643 bytes | |||
-rw-r--r-- | src/arch/dummy.c | 7 | ||||
-rw-r--r-- | src/graphics.c | 21 | ||||
-rw-r--r-- | src/salis.c | 44 | ||||
-rw-r--r-- | src/ui/curses.c | 25 | ||||
-rw-r--r-- | todo.adoc | 5 |
8 files changed, 178 insertions, 42 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c2e872 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# SALIS: A-life Simulator + + +*SALIS simulation running on the V1 architecture with the ncurses user interface* + +## Overview +*SALIS* is a platform for conducting artificial life experiments. It enables +the development of Tierra-like virtual machines, with certain limitations. For +newcomers, I recommend exploring Tierra first. The following resources provide +valuable context and insight into both the motivations and implementation of +both Tierra and this project: + +- [Video about Tierra](https://www.youtube.com/watch?v=Wl5rRGVD0QI) +- [Read about Tierra](http://life.ou.edu/pubs/doc/index.html#What) + +## SALIS V1 Reimplementation +A fully functional clone of the V1 architecture for the SALIS virtual machine +has been implemented using the tools available in this repository. For more +information on the V1 architecture, including its similarities and differences +with the original Tierra simulator, check out the following resources: + +- [SALIS V1 repository](https://git.pauloliver.dev/salis-v1/about/) +- [SALIS V1 introductory playlist](https://www.youtube.com/watch?v=jCFmOCvy6po&list=PLrEmYrpTcDJY2NdGL6B7wIRbKGp_6NkxY) + +## Usage +*SALIS* simulations are initialized using the provided `salis` shell script. +Use `salis new [...]` to start new simulations and `salis load [...]` to load +saved simulations. For a full list of available arguments for each command, +run `salis new --help` and `salis load --help`, respectively. + +The shell script compiles a temporary executable on the fly (compilation +typically takes less than a second) based on the specified arguments and +launches it immediately. + +Different architectures can be implemented as standalone C files in the +`src/arch/` directory. When creating a new simulation, you can select a +specific architecture using the `--arch` argument. + +Similarly, different user interfaces are implemented as C files within the +`src/ui/` directory. For example, the `curses.c` UI launches a terminal-based +simulation visualizer, allowing easy exploration of *SALIS* memory cores and +processes. In contrast, the `daemon.c` UI provides minimal output, making it +ideal for running *SALIS* as a background service. Unlike the `--arch` +argument, you can choose a different `--ui` argument each time you load a +saved simulation. + +For example, the following command will launch a new *SALIS* simulation with 4 +copies of the `55a` ancestor organisms pre-compiled in each memory core. It +will use the `salis-v1` architecture, run on 8 memory cores, with each core +having a size of 2^22 bytes. The PRNG seed is set to `123456789`: +```console +user@host$ ./salis new -A55a -asalis-v1 -c8 -C4 -m22 -nworld-1 -s123456789 -o +``` + +Upon exit, the simulation data will be automatically saved to +`${HOME}/.salis/world-1/`. As long as the contents of this directory are not +removed, you can reload the saved simulation with the following command: +```console +user@host$ ./salis load -n world-1 -o +``` @@ -11,7 +11,7 @@ set -euo pipefail headline="Salis: Simple A-Life Simulator." -help_msg="show help and exit" +help_msg="Shows help and exits" usage() { cat << EOF @@ -22,9 +22,9 @@ Options: -h, --help ${help_msg} Commands: - bench run benchmark test - load load saved simulation - new create a new simulation + bench Runs benchmark + load Loads saved simulation + new Creates a new simulation Use '-h' to list arguments for each command. Example: ${0} bench -h @@ -35,7 +35,7 @@ case ${1:-} in bench|load|new) ;; -h|--help) - usage | less -CQS~ + usage exit 0 ;; "") @@ -60,38 +60,38 @@ arches=`falter arch` uis=`falter ui` anc_def_desc() { - echo "default ancestor file name without extension, " + echo "Default ancestor file name without extension, " echo "to be compiled on all cores " echo "('ANC' points to file 'ancs/<ARCH>/<ANC>.asm')" } anc_spec_def() { - echo "core specific ancestor file names separated by commas, " - echo "using same convention as with default ancestor (<ANC0>,<ANC1>,...), " - echo "when provided will override default ancestor on specified cores" + echo "Core specific ancestor file names separated by commas, " + echo "using same convention as with default ancestor (<ANC0>,<ANC1>,...). " + echo "When provided will override default ancestor on specified cores." } options=( "A|anc-def|ANC|`anc_def_desc`|||bench:new" "a|arch|ARCH|VM architecture|${arches}|dummy|bench:new" - "b|steps|N|number of steps to run in benchmark||0x1000000|bench" - "C|clones|N|number of ancestor clones on each core||1|bench:new" - "c|cores|N|number of simulator cores||2|bench:new" - "F|muta-flip||cosmic rays flip bits instead of randomizing whole bytes||false|bench:new" - "f|force||overwrite existing simulation of given name||false|new" - "H|half||compile ancestor at the middle of the memory buffer||false|bench:new" + "b|steps|N|Number of steps to run in benchmark||0x1000000|bench" + "C|clones|N|Number of ancestor clones on each core||1|bench:new" + "c|cores|N|Number of simulator cores||2|bench:new" + "F|muta-flip||Cosmic rays flip bits instead of randomizing whole bytes||false|bench:new" + "f|force||Overwrites existing simulation of given name||false|new" + "H|half||Compiles ancestor at the middle of the memory buffer||false|bench:new" "h|help||${help_msg}|||bench:load:new" - "M|muta-pow|POW|mutator range exponent (range == 2^POW)||32|bench:new" - "m|mvec-pow|POW|memory vector size exponent (size == 2^POW)||20|bench:new" - "n|name|NAME|name of new or loaded simulation||def.sim|load:new" - "o|optimized||build Salis binary with optimizations||false|bench:load:new" - "p|pre-cmd|CMD|shell command to wrap executable (e.g. gdb)|||bench:load:new" + "M|muta-pow|POW|Mutator range exponent (range == 2^POW)||32|bench:new" + "m|mvec-pow|POW|Memory vector size exponent (size == 2^POW)||20|bench:new" + "n|name|NAME|Name of new or loaded simulation||def.sim|load:new" + "o|optimized||Builds Salis binary with optimizations||false|bench:load:new" + "p|pre-cmd|CMD|Shell command to wrap executable (e.g. gdb, valgrind, etc.)|||bench:load:new" "S|anc-spec|ANC0,ANC1,...|`anc_spec_def`|||bench:new" - "s|seed|SEED|seed value for new simulation||0|bench:new" - "t|thread-gap|N|memory gap between cores (in bytes)||0x100|bench:load:new" - "u|ui|UI|user interface|${uis}|curses|load:new" - "y|sync-pow|POW|core sync interval exponent (interval == 2^POW)||20|bench:new" - "z|auto-save-pow|POW|auto save interval exponent (interval == 2^POW)||36|new" + "s|seed|SEED|Seed value for new simulation||0|bench:new" + "t|thread-gap|N|Memory gap between cores in bytes (could help reduce cache misses?)||0x100|bench:load:new" + "u|ui|UI|User interface|${uis}|curses|load:new" + "y|sync-pow|POW|Core sync interval exponent (interval == 2^POW)||20|bench:new" + "z|auto-save-pow|POW|Auto-save interval exponent (interval == 2^POW)||36|new" ) field() { @@ -111,7 +111,7 @@ fhelp() { lopt=`field "${1}" 2` meta=`field "${1}" 3` - printf %-32s " -${sopt}, --${lopt}`[[ -n ${meta} ]] && echo " ${meta}"`" + printf "%s\r" " -${sopt}, --${lopt}`[[ -n ${meta} ]] && echo " ${meta}"`" help=`field "${1}" 4` choi=`field "${1}" 5` @@ -119,7 +119,7 @@ fhelp() { copt=`[[ -n ${choi} ]] && echo " (choices: ${choi/:/, })"` dopt=`[[ -n ${defv} ]] && echo " (default: ${defv})"` - echo ${help}${copt}${dopt} + echo -e "\t\t\t\t${help}${copt}${dopt}" | fmt -w120 } fshort() { @@ -168,7 +168,7 @@ fiter() { usage() { cat << EOF ${headline} -Usage: ${0} ${cmd} `fiter flist` +Usage: ${0} ${cmd} `fiter flist | fmt -t -w120` Options: `fiter fhelp` @@ -208,7 +208,7 @@ parse_next() { while true ; do case ${1} in -h|--help) - usage | less -CQS~ + usage exit 0 ;; --) Binary files differdiff --git a/src/arch/dummy.c b/src/arch/dummy.c index b440be3..7a47900 100644 --- a/src/arch/dummy.c +++ b/src/arch/dummy.c @@ -11,7 +11,12 @@ bool proc_is_live(const Core *core, u64 pix); #define PROC_FIELDS \ - PROC_FIELD(u64, dmmy) + PROC_FIELD(u64, ip) \ + PROC_FIELD(u64, sp) \ + PROC_FIELD(u64, mb0a) \ + PROC_FIELD(u64, mb0s) \ + PROC_FIELD(u64, mb1a) \ + PROC_FIELD(u64, mb1s) struct Proc { #define PROC_FIELD(type, name) type name; diff --git a/src/graphics.c b/src/graphics.c index 8114f30..780b879 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -89,10 +89,9 @@ void gfx_render_inst(const Core *core, u64 pos, u64 zoom) { for (u64 j = 0; j < zoom; ++j) { u64 addr = pos + (i * zoom) + j; - u8 byte = mvec_get_byte(core, addr); - g_gfx_inst[i] += byte; - g_gfx_mall[i] += (byte & MALL_FLAG) ? 1 : 0; + g_gfx_inst[i] += mvec_get_byte(core, addr); + g_gfx_mall[i] += mvec_is_alloc(core, addr) ? 1 : 0; } } } @@ -102,6 +101,7 @@ void gfx_clear_array(u64 *arry) { memset(arry, 0, g_gfx_vsiz * sizeof(u64)); } +#ifdef MVEC_LOOP void gfx_accumulate_pixel(u64 pos, u64 zoom, u64 pixa, u64 *arry) { assert(arry); @@ -134,6 +134,21 @@ void gfx_accumulate_pixel(u64 pos, u64 zoom, u64 pixa, u64 *arry) { } #endif } +#else +void gfx_accumulate_pixel(u64 pos, u64 zoom, u64 pixa, u64 *arry) { + assert(arry); + + u64 end = pos + (g_gfx_vsiz * zoom); + + if (pixa < pos || pixa >= end) { + return; + } + + u64 pixi = (pixa - pos) / zoom; + assert(pixi < g_gfx_vsiz); + arry[pixi]++; +} +#endif void gfx_render_mbst(const Core *core, u64 pos, u64 zoom) { assert(core); diff --git a/src/salis.c b/src/salis.c index 814dd0d..6714038 100644 --- a/src/salis.c +++ b/src/salis.c @@ -73,51 +73,93 @@ const Proc g_dead_proc; char g_mnemo_table[0x100][MNEMONIC_BUFF_SIZE]; #endif +#ifdef MVEC_LOOP u64 mvec_loop(u64 addr) { return addr % MVEC_SIZE; } +#endif bool mvec_is_alloc(const Core *core, u64 addr) { assert(core); +#ifdef MVEC_LOOP return core->mvec[mvec_loop(addr)] & MALL_FLAG ? true : false; +#else + if (addr < MVEC_SIZE) { + return core->mvec[addr] & MALL_FLAG ? true : false; + } else { + return true; + } +#endif } void mvec_alloc(Core *core, u64 addr) { assert(core); assert(!mvec_is_alloc(core, addr)); +#ifdef MVEC_LOOP core->mvec[mvec_loop(addr)] |= MALL_FLAG; +#else + assert(addr < MVEC_SIZE); + core->mvec[addr] |= MALL_FLAG; +#endif core->mall++; } void mvec_free(Core *core, u64 addr) { assert(core); assert(mvec_is_alloc(core, addr)); +#ifdef MVEC_LOOP core->mvec[mvec_loop(addr)] ^= MALL_FLAG; +#else + assert(addr < MVEC_SIZE); + core->mvec[addr] ^= MALL_FLAG; +#endif core->mall--; } u8 mvec_get_byte(const Core *core, u64 addr) { assert(core); +#ifdef MVEC_LOOP return core->mvec[mvec_loop(addr)]; +#else + if (addr < MVEC_SIZE) { + return core->mvec[addr]; + } else { + return 0; + } +#endif } u8 mvec_get_inst(const Core *core, u64 addr) { assert(core); +#ifdef MVEC_LOOP return core->mvec[mvec_loop(addr)] & INST_MASK; +#else + if (addr < MVEC_SIZE) { + return core->mvec[addr] & INST_MASK; + } else { + return 0; + } +#endif } void mvec_set_inst(Core *core, u64 addr, u8 inst) { assert(core); assert(inst < INST_CAPS); +#ifdef MVEC_LOOP core->mvec[mvec_loop(addr)] &= MALL_FLAG; core->mvec[mvec_loop(addr)] |= inst; +#else + assert(addr < MVEC_SIZE); + core->mvec[addr] &= MALL_FLAG; + core->mvec[addr] |= inst; +#endif } #if MUTA_FLIP_BIT == 1 void mvec_flip_bit(Core *core, u64 addr, int bit) { assert(core); assert(bit < 8); - core->mvec[mvec_loop(addr)] ^= (1 << bit) & INST_MASK; + core->mvec[addr] ^= (1 << bit) & INST_MASK; } #endif diff --git a/src/ui/curses.c b/src/ui/curses.c index 8f91d9f..1d70ca7 100644 --- a/src/ui/curses.c +++ b/src/ui/curses.c @@ -337,7 +337,7 @@ void ui_world_resize() { } } -void ui_print_cell(u64 i, u64 r, u64 x, u64 y) { +void ui_print_cell(u64 i, u64 r, u64 x, u64 y, u64 a) { wchar_t inst_nstr[2] = { L'\0', L'\0' }; cchar_t cchar = { 0 }; u64 inst_avrg = g_gfx_inst[i] / g_wrld_zoom; @@ -350,7 +350,9 @@ void ui_print_cell(u64 i, u64 r, u64 x, u64 y) { int pair_cell; - if (g_wcursor_mode && r == (u64)g_wcursor_x && y == (u64)g_wcursor_y) { + if (a >= MVEC_SIZE) { + pair_cell = PAIR_NORMAL; + } else if (g_wcursor_mode && r == (u64)g_wcursor_x && y == (u64)g_wcursor_y) { pair_cell = PAIR_NORMAL; } else if (g_gfx_ipas[i] != 0) { pair_cell = PAIR_SELECTED_IP; @@ -460,8 +462,9 @@ void ui_print_world(int l) { u64 r = i % g_vlin; u64 x = r + PANE_WIDTH; u64 y = i / g_vlin; + u64 a = g_wrld_pos + (i * g_wrld_zoom); - ui_print_cell(i, r, x, y); + ui_print_cell(i, r, x, y, a); } if (g_wcursor_mode) { @@ -591,7 +594,15 @@ void ev_vscroll(int ev) { g_wrld_pos += g_vlin_rng; break; case 's': +#ifdef MVEC_LOOP g_wrld_pos -= g_vlin_rng; +#else + if (g_wrld_pos < g_vlin_rng) { + g_wrld_pos = 0; + } else { + g_wrld_pos -= g_vlin_rng; + } +#endif break; case 'q': g_wrld_pos = 0; @@ -652,7 +663,15 @@ void ev_hscroll(int ev) { case PAGE_WORLD: switch (ev) { case 'a': +#ifdef MVEC_LOOP g_wrld_pos -= g_wrld_zoom; +#else + if (g_wrld_pos < g_wrld_zoom) { + g_wrld_pos = 0; + } else { + g_wrld_pos -= g_wrld_zoom; + } +#endif break; case 'd': g_wrld_pos += g_wrld_zoom; diff --git a/todo.adoc b/todo.adoc deleted file mode 100644 index 0011c6a..0000000 --- a/todo.adoc +++ /dev/null @@ -1,5 +0,0 @@ -= TODO -Paul Oliver <contact@pauloliver.dev> - -== Architectures -. salis-v3 |