diff --git a/README.md b/README.md index 087b0c8..6546b75 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ following instructions are chosen to be implemented: - [ ] **DIV**: `DIV r1 r2` Divides registers `r1` and `r2` such that `r1 = r1 / r2`. - [ ] **SHL**: `BSL r1 r2` BitShifts `r1` by `r2` to the left. - [ ] **SHR**: `SHR r1 r2` BitShift analog towards the right. +- [ ] **JZ**: `JZ r1 0x10` Jump to `0x10` if value in `r1` is zero. +- [ ] **JNZ**: `JNZ r1 0x10` Jump to `0x10` if value in `r1` is not zero. - [ ] **LB**: `LB r1 0x10` Loads a single byte at `0x10` into register `r1`. - [ ] **SB**: `SB r1 0x10` Stores a single byte in register `r1` to memory location `0x10`. diff --git a/src/preflight_simulator.rs b/src/preflight_simulator.rs index edc29b4..8b5b7e2 100644 --- a/src/preflight_simulator.rs +++ b/src/preflight_simulator.rs @@ -1,9 +1,3 @@ -//! This simulator parses the program and executes it, formulating -//! an `ExecutionTrace` containing all the information of the preflight -//! simulation. -//! -//! This file does not involve itself with any S(N/T)ARK systems - use std::collections::HashMap; use anyhow::{ @@ -77,25 +71,22 @@ impl SimulationRow { &self, prog: &Program, ) -> Result { - // Remember, we have no jump instructions in our VM ISA - // Hence, this following is safe. Otherwise, more complicated - // logic needs to be implemented. - let program_counter = self.program_counter + 1; + // This is mutable precisely because jump instructions can change it + // in weird ways. This is good default for many other operations though + let mut program_counter = self.program_counter + 1; let clock = self.clock + 1; - let instruction = prog - .code - .get(&program_counter) - .cloned() - .context("instruction not found")?; - - let is_halted = instruction == Instruction::Halt; let mut registers = self.registers; let mut memory_snapshot = self .memory_snapshot .clone(); + println!( + "[Exec] clk: {}, pc: {}, inst: {:?}", + self.clock, self.program_counter, self.instruction + ); + match self.instruction { Instruction::Add(a, b) => { registers[usize::from(a)] = registers[usize::from(a)] @@ -121,6 +112,16 @@ impl SimulationRow { registers[usize::from(reg)] = registers[usize::from(reg)] .wrapping_shr(registers[usize::from(amount)].into()); } + Instruction::Jz(reg, instloc) => { + if registers[usize::from(reg)] == 0 { + program_counter = instloc.0 + } + } + Instruction::Jnz(reg, instloc) => { + if registers[usize::from(reg)] != 0 { + program_counter = instloc.0 + } + } Instruction::Lb(reg, memloc) => { registers[usize::from(reg)] = self .memory_snapshot @@ -138,6 +139,14 @@ impl SimulationRow { } }; + let instruction = prog + .code + .get(&program_counter) + .cloned() + .context("instruction not found")?; + + let is_halted = instruction == Instruction::Halt; + Ok(Self { instruction, clock, @@ -171,16 +180,18 @@ pub struct PreflightSimulation { } impl PreflightSimulation { + /// Maximum number of CPU cycles allowed + const MAX_CPU_CYCLES_ALLOWED: usize = 1_000; + /// Entry point to simulate a program and generate a `PreflightSimulation` /// to be used to generate tables pub fn simulate(prog: &Program) -> Result { - const MAX_CPU_CYCLES_ALLOWED: usize = 1_000; - - let mut trace_rows = Vec::with_capacity(MAX_CPU_CYCLES_ALLOWED / 4); + let mut trace_rows = + Vec::with_capacity(Self::MAX_CPU_CYCLES_ALLOWED / 4); let first_row = SimulationRow::generate_first_row(prog)?; trace_rows.push(first_row); - while trace_rows.len() < MAX_CPU_CYCLES_ALLOWED + while trace_rows.len() <= Self::MAX_CPU_CYCLES_ALLOWED && !trace_rows[trace_rows.len() - 1].is_halted { let current_row = @@ -205,6 +216,7 @@ mod tests { use crate::vm_specs::{ Instruction, + InstructionLocation, MemoryLocation, Program, Register, @@ -253,4 +265,58 @@ mod tests { expected.1 ); } + + #[test] + /// Tests whether execution stops on reaching `MAX_CPU_CYCLES_ALLOWED` + fn test_max_cpu_cycles() { + let instructions = vec![ + Instruction::Jz(Register::R0, InstructionLocation(0x00)), + Instruction::Halt, + ]; + + let code = instructions + .into_iter() + .enumerate() + .map(|(idx, inst)| (idx as u8, inst)) + .collect::>(); + + let program = Program { + entry_point: 0, + code, + ..Default::default() + }; + + let simulation = PreflightSimulation::simulate(&program); + assert!(simulation.is_err()); + } + + #[test] + /// Tests whether execution halts + fn test_haltable() { + let instructions = vec![ + Instruction::Lb(Register::R0, MemoryLocation(0x40)), + Instruction::Lb(Register::R1, MemoryLocation(0x41)), + Instruction::Sub(Register::R0, Register::R1), + Instruction::Jnz(Register::R0, InstructionLocation(0x02)), + Instruction::Halt, + ]; + + let code = instructions + .into_iter() + .enumerate() + .map(|(idx, inst)| (idx as u8, inst)) + .collect::>(); + + let memory_init: HashMap = + HashMap::from_iter(vec![(0x40, 0x05), (0x41, 0x01)]); + + let program = Program { + entry_point: 0, + code, + memory_init, + }; + + let simulation = PreflightSimulation::simulate(&program); + assert!(simulation.is_ok()); + } } diff --git a/src/vm_specs.rs b/src/vm_specs.rs index 4d994a9..347ca5c 100644 --- a/src/vm_specs.rs +++ b/src/vm_specs.rs @@ -24,6 +24,10 @@ pub const REGISTER_COUNT: usize = std::mem::variant_count::(); #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct MemoryLocation(pub u8); +/// All instruction locations in this VM are addresses via u8. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct InstructionLocation(pub u8); + #[derive(Clone, Debug, Default, PartialEq)] pub enum Instruction { Add(Register, Register), @@ -32,6 +36,8 @@ pub enum Instruction { Div(Register, Register), Shl(Register, Register), Shr(Register, Register), + Jz(Register, InstructionLocation), + Jnz(Register, InstructionLocation), Lb(Register, MemoryLocation), Sb(Register, MemoryLocation), #[default] @@ -41,6 +47,7 @@ pub enum Instruction { impl Instruction { /// Not the best of the implementations. But written it like this /// for demonstration purposes + /// Prime candidate for Proc Macros :) pub fn get_opcode(&self) -> u8 { match self { Instruction::Add(_, _) => 0, @@ -49,9 +56,11 @@ impl Instruction { Instruction::Div(_, _) => 3, Instruction::Shl(_, _) => 4, Instruction::Shr(_, _) => 5, - Instruction::Lb(_, _) => 6, - Instruction::Sb(_, _) => 7, - Instruction::Halt => 8, + Instruction::Jz(_, _) => 6, + Instruction::Jnz(_, _) => 7, + Instruction::Lb(_, _) => 8, + Instruction::Sb(_, _) => 9, + Instruction::Halt => 10, } } }