aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2026-06-18 00:08:54 +0200
committerPaul Oliver <contact@pauloliver.dev>2026-06-25 00:15:17 +0200
commit0f5581f53f73a92d33826c4771553a9683376e67 (patch)
treeeab8172d21dc4a0b36714af541d22a82cec858f3
parent00a0cb3821ce2bd0fc589ae64ed9cbb791ce44bb (diff)
Adds unit testing scaffold and build tests
-rw-r--r--README.md1
-rw-r--r--arch/dummy/vars.py1
-rw-r--r--arch/v1/vars.py1
-rw-r--r--core/client.cpp8
-rw-r--r--core/logger.c1
-rw-r--r--core/salis.c19
-rwxr-xr-xsalis.py51
-rw-r--r--test/anc/v1/noops.asm38
-rw-r--r--test/test_build.py80
-rw-r--r--test/test_general.py31
-rw-r--r--test/test_v1.py6
-rw-r--r--test/ui/null/ui.c10
-rw-r--r--test/ui/null/vars.py3
-rw-r--r--test/ui/v1/noops/ui.c114
-rw-r--r--test/ui/v1/noops/vars.py3
15 files changed, 335 insertions, 32 deletions
diff --git a/README.md b/README.md
index 8a2ed66..fa9e811 100644
--- a/README.md
+++ b/README.md
@@ -94,7 +94,6 @@ user@host$ ./salis.py client -o -i ${SERVER_IP}
## Dependencies (data client)
- GLFW
-- GLU
- ImGui
- ImPlot
- OpenGL
diff --git a/arch/dummy/vars.py b/arch/dummy/vars.py
index 7150767..fce888a 100644
--- a/arch/dummy/vars.py
+++ b/arch/dummy/vars.py
@@ -7,6 +7,7 @@ inst_set = [(["dummy", f"{i:02x}"], symbol) for i, symbol in enumerate(
core_data_fields = []
core_fields = []
+muta_flip = False
mvec_loop = True
proc_fields = [
("uint64_t", "ip"),
diff --git a/arch/v1/vars.py b/arch/v1/vars.py
index cf4f376..4eac4e7 100644
--- a/arch/v1/vars.py
+++ b/arch/v1/vars.py
@@ -76,6 +76,7 @@ core_data_fields = [
("uint64_t", f"xeva[{2 ** globals()["args"].mvec_pow}]"), # memory block swap events array
]
core_fields = []
+muta_flip = False
mvec_loop = False
proc_fields = [
("uint64_t", "ip"),
diff --git a/core/client.cpp b/core/client.cpp
index d541129..750f2be 100644
--- a/core/client.cpp
+++ b/core/client.cpp
@@ -22,7 +22,6 @@
// [section] includes
// ----------------------------------------------------------------------------
#include <arpa/inet.h>
-#include <GL/glu.h>
#include <GLFW/glfw3.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
@@ -348,8 +347,6 @@ std::array g_hm_colormap = std::to_array<ImVec4>({
{0.988f, 1.000f, 0.643f, 1.f},
});
-PFNGLCOPYIMAGESUBDATAPROC glCopyImageSubData;
-
// ----------------------------------------------------------------------------
// [section] string comparator definition
// ----------------------------------------------------------------------------
@@ -747,7 +744,7 @@ void PlotHeatmap::render_internal(const ImVec2 &frame_size) {
assert(new_tex_id);
if (m_tex_id) {
- glCopyImageSubData(
+ ((PFNGLCOPYIMAGESUBDATAPROC)glfwGetProcAddress("glCopyImageSubData"))(
m_tex_id, GL_TEXTURE_2D, 0, 0, rows_to_chop_off, 0,
new_tex_id, GL_TEXTURE_2D, 0, 0, 0, 0,
g_hm_pixel_count, m_rows_rendered - rows_to_chop_off, 1
@@ -1464,9 +1461,6 @@ void init() {
glfwSwapInterval(0);
#endif
- glCopyImageSubData = (PFNGLCOPYIMAGESUBDATAPROC)glfwGetProcAddress("glCopyImageSubData");
- assert(glCopyImageSubData);
-
log_info("Initializing ImGui");
IMGUI_CHECKVERSION();
ImGui::CreateContext();
diff --git a/core/logger.c b/core/logger.c
index 2c8746f..af41dcc 100644
--- a/core/logger.c
+++ b/core/logger.c
@@ -79,6 +79,7 @@ void log_msg(enum LogLevel level, bool colored, const char *format, va_list args
char buff[LOG_LINE_SIZE];
log_msg_to_buff(buff, LOG_LINE_SIZE, level, colored, format, args);
printf("\r%s\n", buff);
+ fflush(stdout);
}
void log_info_default(const char *format, ...) {
diff --git a/core/salis.c b/core/salis.c
index 9f9a3d0..dac9e52 100644
--- a/core/salis.c
+++ b/core/salis.c
@@ -310,6 +310,7 @@ uint64_t mvec_get_owner(const struct Core *core, uint64_t addr) {
// ----------------------------------------------------------------------------
// [section] mutator functions
// ----------------------------------------------------------------------------
+#if SEED != 0
#if defined(COMMAND_NEW)
uint64_t muta_smix(uint64_t *seed) {
assert(seed);
@@ -357,6 +358,7 @@ void muta_cosmic_ray(struct Core *core) {
#endif
}
}
+#endif
// ----------------------------------------------------------------------------
// [section] process functions
@@ -491,12 +493,15 @@ void core_init(struct Core *core, uint64_t *seed) {
assert(core);
assert(seed);
- if (*seed) {
- core->muta[0] = muta_smix(seed);
- core->muta[1] = muta_smix(seed);
- core->muta[2] = muta_smix(seed);
- core->muta[3] = muta_smix(seed);
- }
+#if SEED != 0
+ assert(*seed);
+ core->muta[0] = muta_smix(seed);
+ core->muta[1] = muta_smix(seed);
+ core->muta[2] = muta_smix(seed);
+ core->muta[3] = muta_smix(seed);
+#else
+ (void)seed;
+#endif
core->pnum = CLONES;
core->pcap = CLONES;
@@ -642,7 +647,9 @@ void core_step(struct Core *core) {
proc_kill(core);
}
+#if SEED != 0
muta_cosmic_ray(core);
+#endif
core_step(core);
}
diff --git a/salis.py b/salis.py
index f247426..f86cd2e 100755
--- a/salis.py
+++ b/salis.py
@@ -2,6 +2,7 @@
# index
# [section] argument parser
+# [section] run unit tests
# [section] build class
# [section] logging
# [section] options dict formatter
@@ -20,6 +21,7 @@ import shutil
import socket
import subprocess
import sys
+import unittest
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, ArgumentTypeError, RawTextHelpFormatter
from tempfile import TemporaryDirectory
@@ -39,9 +41,7 @@ new = sub_parsers.add_parser("new", formatter_class=formatter_class, help="creat
load = sub_parsers.add_parser("load", formatter_class=formatter_class, help="load saved simulation")
server = sub_parsers.add_parser("server", formatter_class=formatter_class, help="run data server")
client = sub_parsers.add_parser("client", formatter_class=formatter_class, help="run data client")
-
-architectures = os.listdir("./arch")
-uis = os.listdir("./ui")
+test = sub_parsers.add_parser("test", formatter_class=formatter_class, help="run unit tests")
def seed(i):
ival = int(i, 0)
@@ -67,14 +67,14 @@ fmt_id = lambda val: val
fmt_hex = lambda val: hex(int(val, 0)) if type(val) == str else hex(val)
options = {
- (("A", "anc"), (new,), fmt_id): {"metavar": "ANC", "help": "ancestor file name without extension; to be compiled on all cores; ANC points to 'anc/{arch}/{ANC}.asm'", "required": True, "type": str},
- (("a", "arch"), (new,), fmt_id): {"choices": architectures, "help": "VM architecture", "default": "dummy", "required": False, "type": str},
+ (("a", "anc"), (new,), fmt_id): {"metavar": "ANC", "help": "ancestor file name without extension; to be compiled on all cores; ANC points to '{anc-path}/{arch}/{ANC}.asm'", "required": True, "type": str},
+ (("A", "anc-path"), (new,), fmt_id): {"metavar": "PATH", "help": "path to search for ancestor files", "default": "anc", "required": False, "type": str},
+ (("b", "build-only"), (new, load, server, client), fmt_id): {"action": "store_true", "help": "only build (do not run) executable", "required": False},
(("C", "clones"), (new,), fmt_id): {"metavar": "N", "help": "number of ancestor clones on each core", "default": 1, "required": False, "type": nat},
(("c", "cores"), (new,), fmt_id): {"metavar": "N", "help": "number of simulator cores", "default": 2, "required": False, "type": nat},
(("d", "data-push-pow"), (new,), fmt_id): {"metavar": "POW", "help": "data aggregation interval exponent; interval = 2^{POW} >= {sync-pow}", "default": 28, "required": False, "type": nat},
- (("F", "font"), (client,), fmt_id): {"metavar": "FILE", "help": "font face to use", "default": "/usr/share/fonts/droid/DroidSansMono.ttf", "required": False},
+ (("f", "font"), (client,), fmt_id): {"metavar": "FILE", "help": "font face to use", "default": "/usr/share/fonts/droid/DroidSansMono.ttf", "required": False},
(("f", "force"), (new,), fmt_id): {"action": "store_true", "help": "overwrite existing simulation of given name", "required": False},
- (("F", "muta-flip"), (new,), fmt_id): {"action": "store_true", "help": "cosmic rays flip bits instead of randomizing whole bytes", "required": False},
(("g", "c-compiler"), (new, load, server, client), fmt_id): {"metavar": "CC", "help": "C compiler to use", "default": "gcc", "required": False, "type": str},
(("G", "c-compiler-flags"), (new, load, server, client), fmt_id): {"metavar": "FLAGS", "help": "base set of flags to pass to C compiler", "default": "-Wall -Wextra -Werror -Wno-overlength-strings -pedantic", "required": False, "type": str},
(("H", "home"), (new, load, server), fmt_id): {"metavar": "PATH", "help": "salis home directory", "default": os.path.join(os.environ["HOME"], ".salis"), "required": False, "type": str},
@@ -83,13 +83,16 @@ options = {
(("m", "mvec-pow"), (new,), fmt_id): {"metavar": "POW", "help": "memory core size exponent; size = 2^{POW}", "default": 20, "required": False, "type": pos},
(("n", "name"), (new, load, server), fmt_id): {"metavar": "NAME", "help": "name of new or loaded simulation", "default": "def.sim", "required": False, "type": str},
(("o", "optimized"), (new, load, server, client), fmt_id): {"action": "store_true", "help": "build with optimizations", "required": False},
+ (("p", "pattern"), (test,), fmt_id): {"metavar": "PATTERN", "help": "pattern for unit test discovery", "default": "*", "required": False, "type": str},
(("P", "port"), (server, client), fmt_id): {"metavar": "PORT", "help": "port number for data server", "default": 8080, "required": False, "type": port},
(("p", "pre-cmd"), (new, load, server, client), fmt_id): {"metavar": "CMD", "help": "shell command with which to wrap call to executable; e.g. gdb, time, valgrind, etc.", "required": False, "type": str},
(("s", "seed"), (new,), fmt_hex): {"metavar": "SEED", "help": "seed value for new simulation; a value of 0 disables cosmic rays; a value of -1 creates a random seed", "default": 0, "required": False, "type": seed},
(("T", "keep-temp-dir"), (new, load, server, client), fmt_id): {"action": "store_true", "help": "keep temporary directory on exit", "required": False},
(("t", "thread-gap"), (new, load), fmt_hex): {"metavar": "N", "help": "memory gap between core elements in bytes; may help reduce cache misses", "default": 0x100, "required": False, "type": nat},
- (("u", "ui"), (new, load), fmt_id): {"choices": uis, "help": "user interface", "default": "curses", "required": False, "type": str},
+ (("u", "ui"), (new, load), fmt_id): {"metavar": "UI", "help": "user interface", "default": "curses", "required": False, "type": str},
+ (("U", "ui-path"), (new, load), fmt_id): {"metavar": "PATH", "help": "path to search for UIs", "default": "ui", "required": False, "type": str},
(("v", "no-vsync"), (client,), fmt_id): {"action": "store_true", "help": "disable vsync", "required": False},
+ (("v", "vm-arch"), (new,), fmt_id): {"metavar": "ARCH", "help": "VM architecture", "default": "dummy", "required": False, "type": str},
(("x", "cpp-compiler"), (client,), fmt_id): {"metavar": "CXX", "help": "C++ compiler to use", "default": "g++", "required": False, "type": str},
(("X", "cpp-compiler-flags"), (client,), fmt_id): {"metavar": "FLAGS", "help": "base set of flags to pass to C++ compiler", "default": "-Wall -Wextra -Werror -pedantic -std=c++20 -lstdc++", "required": False, "type": str},
(("y", "sync-pow"), (new,), fmt_id): {"metavar": "POW", "help": "core sync interval exponent; sync events occur every N steps, where N = 2^{POW}", "default": 20, "required": False, "type": pos},
@@ -103,6 +106,16 @@ for (name, sub_parsers, _), kwargs in options.items():
args = parser.parse_args()
# ------------------------------------------------------------------------------
+# [section] run unit tests
+# ------------------------------------------------------------------------------
+if args.command == "test":
+ test_runner = unittest.TextTestRunner(verbosity=2)
+ test_loader = unittest.TestLoader()
+ test_loader.testNamePatterns = [args.pattern]
+ test_runner.run(test_loader.discover("test"))
+ sys.exit(0)
+
+# ------------------------------------------------------------------------------
# [section] build class
# ------------------------------------------------------------------------------
class Build:
@@ -296,14 +309,14 @@ if args.command == "client":
# ------------------------------------------------------------------------------
# [section] source architecture variables
# ------------------------------------------------------------------------------
-arch_path = os.path.join("arch", args.arch, "vars.py")
+arch_path = os.path.join("arch", args.vm_arch, "vars.py")
log.info(f"Loading architecture variables from: {arch_path}")
arch_vars = runpy.run_path(arch_path, init_globals=globals())
# ------------------------------------------------------------------------------
# [section] ancestor assembler
# ------------------------------------------------------------------------------
-anc_path = os.path.join("anc", args.arch, f"{args.anc}.asm")
+anc_path = os.path.join(args.anc_path, args.vm_arch, f"{args.anc}.asm")
if not os.path.isfile(anc_path):
raise RuntimeError(f"Could not find ancestor file: {anc_path}")
@@ -337,11 +350,11 @@ log.info(f"Compiled ancestor file '{anc_path}' into byte array: {anc_repr}")
# [section] compiler flags
# ------------------------------------------------------------------------------
def pop_ui_vars():
- ns.ui_path = os.path.join("ui", args.ui, "vars.py")
- ns.ui_vars = runpy.run_path(ns.ui_path, init_globals=globals())
- log.info(f"Sourced UI variables from: {ns.ui_path}")
+ ns.ui_vars_path = os.path.join(args.ui_path, args.ui, "vars.py")
+ ns.ui_vars = runpy.run_path(ns.ui_vars_path, init_globals=globals())
+ log.info(f"Sourced UI variables from: {ns.ui_vars_path}")
- ns.b.flags.add(f"-Iui/{args.ui}")
+ ns.b.flags.add(f"-I{args.ui_path}/{args.ui}")
ns.b.flags.update({*ns.ui_vars["flags"]})
ns.b.defines.update({*ns.ui_vars["defines"]})
ns.b.links.update({*ns.ui_vars["links"]})
@@ -362,10 +375,10 @@ def pop_net_vars():
ns.b.links.add("-ljson-c")
def pop_general():
- ns.b.flags.add(f"-Iarch/{args.arch}")
+ ns.b.flags.add(f"-Iarch/{args.vm_arch}")
ns.b.flags.add("-Icore")
- if args.muta_flip:
+ if arch_vars["muta_flip"]:
ns.b.defines.add("-DMUTA_FLIP")
if arch_vars["mvec_loop"]:
@@ -374,7 +387,7 @@ def pop_general():
ns.b.defines.add(f"-DANC=\"{args.anc}\"")
ns.b.defines.add(f"-DANC_BYTES={anc_repr}")
ns.b.defines.add(f"-DANC_SIZE={len(anc_bytes)}")
- ns.b.defines.add(f"-DARCH=\"{args.arch}\"")
+ ns.b.defines.add(f"-DARCH=\"{args.vm_arch}\"")
ns.b.defines.add(f"-DAUTOSAVE_INTERVAL={2 ** args.auto_save_pow}ul")
ns.b.defines.add(f"-DCLONES={args.clones}")
ns.b.defines.add(f"-DCOMMAND_{args.command.upper()}")
@@ -445,4 +458,6 @@ if args.command == "client":
# [section] build and launch
# ------------------------------------------------------------------------------
ns.b.build()
-ns.b.exec()
+
+if not args.build_only:
+ ns.b.exec()
diff --git a/test/anc/v1/noops.asm b/test/anc/v1/noops.asm
new file mode 100644
index 0000000..77862c4
--- /dev/null
+++ b/test/anc/v1/noops.asm
@@ -0,0 +1,38 @@
+noop
+nop0
+nop1
+nop2
+nop3
+
+keya
+keyb
+keyc
+keyd
+keye
+keyf
+keyg
+keyh
+keyi
+keyj
+keyk
+keyl
+keym
+keyn
+keyo
+keyp
+loka
+lokb
+lokc
+lokd
+loke
+lokf
+lokg
+lokh
+loki
+lokj
+lokk
+lokl
+lokm
+lokn
+loko
+lokp
diff --git a/test/test_build.py b/test/test_build.py
new file mode 100644
index 0000000..e0fecef
--- /dev/null
+++ b/test/test_build.py
@@ -0,0 +1,80 @@
+import signal
+
+from test_general import SalisTest
+
+class TestBuild(SalisTest):
+ def cmds_new(self, anc, arch):
+ return (
+ f"./salis.py new -a{anc} -b -f -g{compiler} -H{self.tempdir.name} {optimized} -s{seed} -u{ui} -v{arch}"
+ for compiler in ["clang", "gcc"]
+ for optimized in ["", "-o"]
+ for seed in [-1, 0, 1234]
+ for ui in ["curses", "daemon"]
+ )
+
+ def cmds_new_dummy(self):
+ return self.cmds_new("0123", "dummy")
+
+ def cmds_new_v1(self):
+ return self.cmds_new("55a", "v1")
+
+ def cmds_load(self):
+ return (
+ f"./salis.py load -b -g{compiler} -H{self.tempdir.name} {optimized} -u{ui}"
+ for compiler in ["clang", "gcc"]
+ for optimized in ["", "-o"]
+ for ui in ["curses", "daemon"]
+ )
+
+ def cmds_server(self):
+ return (
+ f"./salis.py server -b -g{compiler} -H{self.tempdir.name} {optimized}"
+ for compiler in ["clang", "gcc"]
+ for optimized in ["", "-o"]
+ )
+
+ def cmds_client(self):
+ return (
+ f"./salis.py client -b -g{c_compiler} {optimized} -x{cxx_compiler}"
+ for c_compiler in ["clang", "gcc"]
+ for cxx_compiler in ["clang", "gcc"]
+ for optimized in ["", "-o"]
+ )
+
+ def test_dummy_new(self):
+ for cmd in self.cmds_new_dummy():
+ SalisTest.run_subprocess(cmd)
+
+ def test_v1_new(self):
+ for cmd in self.cmds_new_v1():
+ SalisTest.run_subprocess(cmd)
+
+ def test_dummy_load(self):
+ SalisTest.run_subprocess(next(self.cmds_new_dummy()))
+
+ for cmd in self.cmds_load():
+ SalisTest.run_subprocess(cmd)
+
+ def test_v1_load(self):
+ SalisTest.run_subprocess(next(self.cmds_new_v1()))
+
+ for cmd in self.cmds_load():
+ SalisTest.run_subprocess(cmd)
+
+ def test_server(self):
+ SalisTest.run_subprocess(next(self.cmds_new_dummy()))
+
+ for cmd in self.cmds_server():
+ SalisTest.run_subprocess(cmd)
+
+ @SalisTest.run_ui_test(anc="0123", anc_path="anc", name="def.sim", ui="null", ui_path="test/ui", vm_arch="dummy")
+ def test_client(self):
+ with SalisTest.run_pipe(f"./salis.py server -H{self.tempdir.name}") as server_proc:
+ for line in server_proc.stdout:
+ if "Listening..." in line:
+ break
+
+ for cmd in self.cmds_client():
+ SalisTest.run_subprocess(cmd)
+
+ server_proc.send_signal(signal.SIGINT)
diff --git a/test/test_general.py b/test/test_general.py
new file mode 100644
index 0000000..eb3185d
--- /dev/null
+++ b/test/test_general.py
@@ -0,0 +1,31 @@
+import subprocess
+import unittest
+
+from tempfile import TemporaryDirectory
+
+class SalisTest(unittest.TestCase):
+ def setUp(self):
+ self.tempdir = TemporaryDirectory(prefix="salis_test_")
+
+ def tearDown(self):
+ self.tempdir.cleanup()
+
+ @staticmethod
+ def run_subprocess(cmd):
+ subprocess.run(cmd.split(), check=True, stdout=subprocess.DEVNULL)
+
+ @staticmethod
+ def run_pipe(cmd):
+ return subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, text=True)
+
+ @staticmethod
+ def run_ui_test(anc, anc_path, ui, ui_path, vm_arch, clones=1, cores=1, name="def.sim"):
+ def test_decorator(test_case):
+ def test_wrapper(self):
+ cmd = f"./salis.py new -a{anc} -A{anc_path} -C{clones} -c{cores} -H{self.tempdir.name} -n{name} -u{ui} -U{ui_path} -v{vm_arch}"
+ subprocess.run(cmd.split(), check=True, stdout=subprocess.DEVNULL)
+ test_case(self)
+
+ return test_wrapper
+
+ return test_decorator
diff --git a/test/test_v1.py b/test/test_v1.py
new file mode 100644
index 0000000..466e4bd
--- /dev/null
+++ b/test/test_v1.py
@@ -0,0 +1,6 @@
+from test_general import SalisTest
+
+class TestV1(SalisTest):
+ @SalisTest.run_ui_test(anc="noops", anc_path="test/anc", ui="noops", ui_path="test/ui/v1", vm_arch="v1")
+ def test_ui_v1_noops(self):
+ pass
diff --git a/test/ui/null/ui.c b/test/ui/null/ui.c
new file mode 100644
index 0000000..5872342
--- /dev/null
+++ b/test/ui/null/ui.c
@@ -0,0 +1,10 @@
+int main(void) {
+#if defined(COMMAND_NEW)
+ salis_init();
+#elif defined(COMMAND_LOAD)
+ salis_load();
+#endif
+ salis_save(SIM_PATH);
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/null/vars.py b/test/ui/null/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/null/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()
diff --git a/test/ui/v1/noops/ui.c b/test/ui/v1/noops/ui.c
new file mode 100644
index 0000000..230e98d
--- /dev/null
+++ b/test/ui/v1/noops/ui.c
@@ -0,0 +1,114 @@
+#if !defined(COMMAND_NEW)
+#error
+#endif
+
+void check_invariants(void) {
+ assert(g_cores->mall == ANC_SIZE);
+ assert(g_cores->muta[0] == 0);
+ assert(g_cores->muta[1] == 0);
+ assert(g_cores->muta[2] == 0);
+ assert(g_cores->muta[3] == 0);
+
+ assert(g_cores->pnum == 1);
+ assert(g_cores->pcap == 1);
+ assert(g_cores->pfst == 0);
+ assert(g_cores->plst == 0);
+ assert(g_cores->pcur == 0);
+ assert(g_cores->psli == 0);
+
+ for (uint64_t i = 0; i < SYNC_INTERVAL; i++) {
+ assert(g_cores->ivav[i] == 0);
+ assert(g_cores->iviv[i] == 0);
+ }
+
+ assert(g_cores->emb1 == 0);
+ assert(g_cores->eliv == 0);
+ assert(g_cores->edea == 0);
+
+ for (uint64_t i = 0; i < MVEC_SIZE; i++) {
+ assert(g_cores->aeva[i] == 0);
+ assert(g_cores->beva[i] == 0);
+ }
+
+ for (uint64_t i = 0; i < INST_COUNT; i++) {
+ assert(g_cores->ipop[i] == 0);
+ assert(g_cores->iwrt[i] == 0);
+ }
+
+ assert(g_cores->wmb0 == 0);
+ assert(g_cores->wmb1 == 0);
+ assert(g_cores->wdea == 0);
+
+ for (uint64_t i = 0; i < MVEC_SIZE; i++) {
+ assert(g_cores->weva[i] == 0);
+ assert(g_cores->xeva[i] == 0);
+ }
+
+ assert(g_cores->pvec->mb0a == 0);
+ assert(g_cores->pvec->mb0s == ANC_SIZE);
+ assert(g_cores->pvec->mb1a == 0);
+ assert(g_cores->pvec->mb1s == 0);
+ assert(g_cores->pvec->r0x == 0);
+ assert(g_cores->pvec->r1x == 0);
+ assert(g_cores->pvec->r2x == 0);
+ assert(g_cores->pvec->r3x == 0);
+ assert(g_cores->pvec->s0 == 0);
+ assert(g_cores->pvec->s1 == 0);
+ assert(g_cores->pvec->s2 == 0);
+ assert(g_cores->pvec->s3 == 0);
+ assert(g_cores->pvec->s4 == 0);
+ assert(g_cores->pvec->s5 == 0);
+ assert(g_cores->pvec->s6 == 0);
+ assert(g_cores->pvec->s7 == 0);
+}
+
+void check_variants(uint64_t step) {
+ assert(g_cores->cycl == step);
+ assert(g_cores->ivpt == step);
+
+ assert(g_cores->emb0 == step);
+
+ for (uint64_t i = 0; i < MVEC_SIZE; i++) {
+ assert(g_cores->eeva[i] == (i < step ? 1 : 0));
+ }
+
+ for (uint64_t i = 0; i < INST_COUNT; i++) {
+ uint64_t exed = 0;
+
+ for (uint64_t j = 0; j < step; j++) {
+ if (mvec_get_inst(g_cores, j) == i) {
+ exed = 1;
+ }
+ }
+
+ assert(g_cores->iexe[i] == exed);
+ }
+
+ assert(g_cores->pvec->ip == step);
+ assert(g_cores->pvec->sp == step);
+}
+
+void check_final(void) {
+ assert(g_cores->pvec->ip == ANC_SIZE);
+ assert(g_cores->pvec->sp == ANC_SIZE);
+}
+
+int main(void) {
+ uint64_t step = 0;
+
+ salis_init();
+ check_invariants();
+ check_variants(step);
+
+ while (mvec_is_proc_owner(g_cores, g_cores->pvec->ip, g_cores->pcur)) {
+ salis_step(1);
+ step++;
+
+ check_invariants();
+ check_variants(step);
+ }
+
+ check_final();
+ salis_free();
+ return 0;
+}
diff --git a/test/ui/v1/noops/vars.py b/test/ui/v1/noops/vars.py
new file mode 100644
index 0000000..9c2a9f3
--- /dev/null
+++ b/test/ui/v1/noops/vars.py
@@ -0,0 +1,3 @@
+flags = set()
+defines = set()
+links = set()