perilus

git clone https://git.bradleygannon.com/perilus.git
Log | Files | Refs | Submodules | README

commit 2b4e352f88e5d5bccc4b05989b5c19792a371dcd
parent 8cdbd4e268ad77d7166cd5160ffaaa837432d833
Author: Bradley Gannon <bradley@bradleygannon.com>
Date:   Wed, 22 Jul 2026 18:03:26 -0400

Add test subcommand and load_file method to simulator

Diffstat:
Msim/Cargo.lock | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msim/Cargo.toml | 1+
Msim/src/main.rs | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msim/src/perilus.rs | 24+++++++++++++++++++++---
4 files changed, 206 insertions(+), 9 deletions(-)

diff --git a/sim/Cargo.lock b/sim/Cargo.lock @@ -33,6 +33,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -160,6 +210,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] +name = "clap" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] name = "color-eyre" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -187,6 +277,12 @@ dependencies = [ ] [[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] name = "compact_str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -585,6 +681,12 @@ dependencies = [ ] [[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -821,6 +923,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1307,6 +1415,7 @@ version = "0.1.0" dependencies = [ "bounded-integer", "cc", + "clap", "color-eyre", "crossterm", "ratatui", diff --git a/sim/Cargo.toml b/sim/Cargo.toml @@ -8,6 +8,7 @@ cc = "1.2.60" [dependencies] bounded-integer = "0.6.1" +clap = { version = "4.6.2", features = ["derive"] } color-eyre = "0.6.5" crossterm = "0.29.0" ratatui = "0.30.0" diff --git a/sim/src/main.rs b/sim/src/main.rs @@ -1,5 +1,6 @@ -use std::{fmt::Display, time::Duration}; +use std::{error::Error, fmt::Display, path::PathBuf, time::Duration}; +use clap::{Parser, Subcommand}; use crossterm::event::{self, Event, KeyCode, KeyEvent}; use ratatui::{ DefaultTerminal, Frame, @@ -13,6 +14,23 @@ use crate::perilus::{MemoryIndex, Perilus, Register}; mod perilus; +#[derive(Parser)] +struct Cli { + #[command(subcommand)] + command: Option<Command>, +} + +#[derive(Subcommand)] +enum Command { + Interactive { + /// File to load verbatim into memory starting at 0x0 + file: Option<PathBuf>, + }, + Test { + file: PathBuf, + }, +} + enum Mode { Normal, EditPc, @@ -389,12 +407,63 @@ impl Widget for &App { } } -fn main() -> color_eyre::Result<()> { - let perilus = Perilus::init(); - let mut app = App::new(perilus); - +fn run_interactive(mut app: App) -> color_eyre::Result<()> { color_eyre::install()?; ratatui::run(|terminal| app.run(terminal))?; - Ok(()) } + +const EBREAK_INSTRUCTION: u32 = 0x00100073; +const TEST_MAX_CYCLES: u32 = 1_000_000; +const TEST_PASS_VALUE: u32 = 0xebbb3689; +const TEST_FAIL_VALUE: u32 = 0xd02a545a; + +fn main() -> Result<(), Box<dyn Error>> { + let cli = Cli::parse(); + let perilus = Perilus::init(); + + match cli.command { + Some(Command::Interactive { file }) => { + if let Some(file) = file { + perilus.load_file(file)?; + } + let app = App::new(perilus); + run_interactive(app).map_err(|e| -> Box<dyn Error> { e.to_string().into() })?; + Ok(()) + } + Some(Command::Test { file }) => { + perilus.load_file(file)?; + let mut cycles = 0; + loop { + perilus.pulse_clock(); + cycles += 1; + if cycles > TEST_MAX_CYCLES { + return Err("Test execution hit maximum cycle count limit".into()); + } + if perilus.get_control_unit_state() == 0 { + let pc = perilus.get_pc(); + let instr = perilus.get_memory().get((pc / 4) as usize); + if instr.is_none() { + return Err(format!( + "Program counter diverged to an address outside memory: 0x{pc:08x}" + ) + .into()); + } + if let Some(&EBREAK_INSTRUCTION) = instr { + let result = perilus.get_registers()[1]; + match result { + TEST_PASS_VALUE => return Ok(()), + TEST_FAIL_VALUE => return Err("Test failed".into()), + v => return Err(format!("Got unexpected test result 0x{v:08x}").into()), + } + } + } + } + } + None => { + let app = App::new(perilus); + run_interactive(app).map_err(|e| -> Box<dyn Error> { e.to_string().into() })?; + Ok(()) + } + } +} diff --git a/sim/src/perilus.rs b/sim/src/perilus.rs @@ -1,5 +1,5 @@ use bounded_integer::BoundedU32; -use std::slice; +use std::{io, path::PathBuf, slice}; unsafe extern "C" { fn perilus_init(); @@ -18,13 +18,13 @@ unsafe extern "C" { } pub(crate) type Register = BoundedU32<0, 31>; -pub(crate) type MemoryIndex = BoundedU32<0, 1024>; +pub(crate) type MemoryIndex = BoundedU32<0, { Perilus::MEMORY_SIZE_WORDS as u32 }>; pub(crate) struct Perilus; impl Perilus { const NUM_REGISTERS: usize = 32; - const MEMORY_SIZE_WORDS: usize = 1024; + const MEMORY_SIZE_WORDS: usize = 1_048_576; pub(crate) fn init() -> Perilus { unsafe { perilus_init() }; @@ -68,6 +68,24 @@ impl Perilus { pub(crate) fn set_memory(&self, address: u32, value: u32) { unsafe { perilus_set_memory(address, value) }; } + pub(crate) fn load_file(&self, file: PathBuf) -> io::Result<()> { + let bytes = std::fs::read(file)?; + if bytes.len() > Self::MEMORY_SIZE_WORDS * 4 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Provided file is too large to fit in simulated memory", + )); + } + for (address_bytes, value) in bytes.iter().enumerate() { + let address_words = address_bytes / 4; + let shift = (address_bytes % 4) * 8; + let current = self.get_memory()[address_words]; + let mask = !(0xffu32 << shift); + let new = (current & mask) | ((*value as u32) << shift); + self.set_memory(address_words as u32, new); + } + Ok(()) + } } impl Drop for Perilus {