aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md60
-rwxr-xr-xsalis58
-rw-r--r--sim.pngbin0 -> 311643 bytes
-rw-r--r--src/arch/dummy.c7
-rw-r--r--src/graphics.c21
-rw-r--r--src/salis.c44
-rw-r--r--src/ui/curses.c25
-rw-r--r--todo.adoc5
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](sim.png)
+*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
+```
diff --git a/salis b/salis
index a493861..243f947 100755
--- a/salis
+++ b/salis
@@ -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
;;
--)
diff --git a/sim.png b/sim.png
new file mode 100644
index 0000000..23eb84c
--- /dev/null
+++ b/sim.png
Binary files differ
diff --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