From 35796f71d256db2cdf93a23f31a031498e245405 Mon Sep 17 00:00:00 2001 From: svv232 Date: Mon, 11 Nov 2024 23:45:01 -0500 Subject: [PATCH 1/6] create interpreter implementation and adding instruction decoder --- o1vm/src/interpreters/riscv32i/witness.rs | 60 ++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/o1vm/src/interpreters/riscv32i/witness.rs b/o1vm/src/interpreters/riscv32i/witness.rs index 3a36c96f65..28e5170704 100644 --- a/o1vm/src/interpreters/riscv32i/witness.rs +++ b/o1vm/src/interpreters/riscv32i/witness.rs @@ -7,7 +7,7 @@ use super::{ INSTRUCTION_SET_SIZE, SCRATCH_SIZE, }; use crate::{ - cannon::{PAGE_ADDRESS_MASK, PAGE_ADDRESS_SIZE, PAGE_SIZE}, + cannon::{State, PAGE_ADDRESS_MASK, PAGE_ADDRESS_SIZE, PAGE_SIZE}, lookups::Lookup, }; use ark_ff::Field; @@ -567,6 +567,64 @@ impl InterpreterEnv for Env { } impl Env { + pub fn create(page_size: usize, state: State) -> Self { + let initial_instruction_pointer = state.pc; + let next_instruction_pointer = state.next_pc; + + let selector = INSTRUCTION_SET_SIZE; + + let mut initial_memory: Vec<(u32, Vec)> = state + .memory + .into_iter() + // Check that the conversion from page data is correct + .map(|page| (page.index, page.data)) + .collect(); + + for (_address, initial_memory) in initial_memory.iter_mut() { + initial_memory.extend((0..(page_size - initial_memory.len())).map(|_| 0u8)); + assert_eq!(initial_memory.len(), page_size); + } + + let memory_offsets = initial_memory + .iter() + .map(|(offset, _)| *offset) + .collect::>(); + + let initial_registers = { + Registers { + general_purpose: state.registers, + current_instruction_pointer: initial_instruction_pointer, + next_instruction_pointer, + heap_pointer: state.heap, + } + }; + + let mut registers = initial_registers.clone(); + registers[2] = 0x408004f0; + // set the stack pointer to the top of the stack + + Env { + instruction_counter: state.step, + memory: initial_memory.clone(), + last_memory_accesses: [0usize; 3], + memory_write_index: memory_offsets + .iter() + .map(|offset| (*offset, vec![0u64; page_size])) + .collect(), + last_memory_write_index_accesses: [0usize; 3], + registers, + registers_write_index: Registers::default(), + scratch_state_idx: 0, + scratch_state: fresh_scratch_state(), + halt: state.exited, + selector, + } + } + + pub fn next_instruction_counter(&self) -> u64 { + (self.normalized_instruction_counter() + 1) * MAX_ACC + } + pub fn reset_scratch_state(&mut self) { self.scratch_state_idx = 0; self.scratch_state = fresh_scratch_state(); From 47eacb05670f3d6ede33f70205b07a62496959c1 Mon Sep 17 00:00:00 2001 From: svv232 Date: Tue, 12 Nov 2024 00:03:59 -0500 Subject: [PATCH 2/6] correcting instruction decoding to follow spec --- o1vm/src/interpreters/riscv32i/interpreter.rs | 15 ++- o1vm/src/interpreters/riscv32i/witness.rs | 122 +++++++++++++++++- 2 files changed, 129 insertions(+), 8 deletions(-) diff --git a/o1vm/src/interpreters/riscv32i/interpreter.rs b/o1vm/src/interpreters/riscv32i/interpreter.rs index e419c69b6b..7d4c7dd6d4 100644 --- a/o1vm/src/interpreters/riscv32i/interpreter.rs +++ b/o1vm/src/interpreters/riscv32i/interpreter.rs @@ -55,9 +55,11 @@ pub enum IInstruction { SetLessThanImmediateUnsigned, // sltiu AddImmediate, // addi - AndImmediate, // andi XorImmediate, // xori OrImmediate, // ori + AndImmediate, // andi + + JumpAndLinkRegister, // jalr } #[derive( @@ -78,9 +80,9 @@ pub enum SBInstruction { BranchEq, // beq BranchNeq, // bne BranchLessThan, // blt - BranchGe, // bge + BranchGreaterThanEqual, // bge BranchLessThanUnsigned, // bltu - BranchGreaterThanEqual, // bgeu + BranchGreaterThanEqualUnsigned, // bgeu } #[derive( @@ -99,7 +101,6 @@ pub enum UInstruction { pub enum UJInstruction { #[default] JumpAndLink, // jal - JumpAndLinkRegister, // jalr } #[derive( @@ -220,6 +221,7 @@ impl std::fmt::Display for IInstruction { IInstruction::XorImmediate => write!(f, "xori"), IInstruction::OrImmediate => write!(f, "ori"), IInstruction::AndImmediate => write!(f, "andi"), + IInstruction::JumpAndLinkRegister => write!(f, "jalr"), } } } @@ -240,9 +242,9 @@ impl std::fmt::Display for SBInstruction { SBInstruction::BranchEq => write!(f, "beq"), SBInstruction::BranchNeq => write!(f, "bne"), SBInstruction::BranchLessThan => write!(f, "blt"), - SBInstruction::BranchGe => write!(f, "bge"), + SBInstruction::BranchGreaterThanEqual=> write!(f, "bge"), SBInstruction::BranchLessThanUnsigned => write!(f, "bltu"), - SBInstruction::BranchGreaterThanEqual => write!(f, "bgeu"), + SBInstruction::BranchGreaterThanEqualUnsigned => write!(f, "bgeu"), } } } @@ -260,7 +262,6 @@ impl std::fmt::Display for UJInstruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { UJInstruction::JumpAndLink => write!(f, "jal"), - UJInstruction::JumpAndLinkRegister => write!(f, "jalr"), } } } diff --git a/o1vm/src/interpreters/riscv32i/witness.rs b/o1vm/src/interpreters/riscv32i/witness.rs index 28e5170704..0b607d337a 100644 --- a/o1vm/src/interpreters/riscv32i/witness.rs +++ b/o1vm/src/interpreters/riscv32i/witness.rs @@ -2,7 +2,10 @@ // to the SAME register/memory addrss? use super::{ column::Column, - interpreter::{Instruction, InterpreterEnv}, + interpreter::{ + IInstruction, Instruction, InterpreterEnv, RInstruction, SBInstruction, SInstruction, + SyscallInstruction, UInstruction, UJInstruction, + }, registers::Registers, INSTRUCTION_SET_SIZE, SCRATCH_SIZE, }; @@ -625,6 +628,123 @@ impl Env { (self.normalized_instruction_counter() + 1) * MAX_ACC } + pub fn decode_instruction(&mut self) -> (Instruction, u32) { + /* https://www.cs.cornell.edu/courses/cs3410/2024fa/assignments/cpusim/riscv-instructions.pdf */ + let instruction = + ((self.get_memory_direct(self.registers.current_instruction_pointer) as u32) << 24) + | ((self.get_memory_direct(self.registers.current_instruction_pointer + 1) as u32) + << 16) + | ((self.get_memory_direct(self.registers.current_instruction_pointer + 2) as u32) + << 8) + | (self.get_memory_direct(self.registers.current_instruction_pointer + 3) as u32); + let instruction = instruction.to_be(); // convert to big endian for more straightforward decoding + println!( + "Decoding instruction at address {:x} with value {:b}, with opcode", + self.registers.current_instruction_pointer, instruction + ); + + let opcode = { + match instruction & 0b1111111 // bits 0-6 + { + 0b0110111 => Instruction::UType(UInstruction::LoadUpperImmediate), + 0b0010111 => Instruction::UType(UInstruction::AddUpperImmediate), + 0b1101111 => Instruction::UJType(UJInstruction::JumpAndLink), + 0b1100011 => + match (instruction >> 12) & 0x7 // bits 12-14 for func3 + { + 0b000 => Instruction::SBType(SBInstruction::BranchEq), + 0b001 => Instruction::SBType(SBInstruction::BranchNeq), + 0b100 => Instruction::SBType(SBInstruction::BranchLessThan), + 0b101 => Instruction::SBType(SBInstruction::BranchGreaterThanEqual), + 0b110 => Instruction::SBType(SBInstruction::BranchLessThanUnsigned), + 0b111 => Instruction::SBType(SBInstruction::BranchGreaterThanEqualUnsigned), + _ => panic!("Unknown SBType instruction with full inst {}", instruction), + }, + 0b1100111 => Instruction::IType(IInstruction::JumpAndLinkRegister), + 0b0000011 => + match (instruction >> 12) & 0x7 // bits 12-14 for func3 + { + 0b000 => Instruction::IType(IInstruction::LoadByte), + 0b001 => Instruction::IType(IInstruction::LoadHalf), + 0b010 => Instruction::IType(IInstruction::LoadWord), + 0b100 => Instruction::IType(IInstruction::LoadByteUnsigned), + 0b101 => Instruction::IType(IInstruction::LoadHalfUnsigned), + _ => panic!("Unknown IType instruction with full inst {}", instruction), + }, + 0b0100011 => + match (instruction >> 12) & 0x7 // bits 12-14 for func3 + { + 0b000 => Instruction::SType(SInstruction::StoreByte), + 0b001 => Instruction::SType(SInstruction::StoreHalf), + 0b010 => Instruction::SType(SInstruction::StoreWord), + _ => panic!("Unknown SType instruction with full inst {}", instruction), + }, + 0b0010011 => + match (instruction >> 12) & 0x7 // bits 12-14 for func3 + { + 0b000 => Instruction::IType(IInstruction::AddImmediate), + 0b010 => Instruction::IType(IInstruction::SetLessThanImmediate), + 0b011 => Instruction::IType(IInstruction::SetLessThanImmediateUnsigned), + 0b100 => Instruction::IType(IInstruction::XorImmediate), + 0b110 => Instruction::IType(IInstruction::OrImmediate), + 0b111 => Instruction::IType(IInstruction::AndImmediate), + 0b001 => Instruction::IType(IInstruction::ShiftLeftLogicalImmediate), + 0b101 => + match (instruction >> 30) & 0x1 // bit 30 in simm component of IType + { + 0b0 => Instruction::IType(IInstruction::ShiftRightLogicalImmediate), + 0b1 => Instruction::IType(IInstruction::ShiftRightArithmeticImmediate), + _ => panic!("Unknown IType in shift right instructions with full inst {}", instruction), + }, + _ => panic!("Unknown IType instruction with full inst {}", instruction), + }, + 0b0110011 => + match (instruction >> 12) & 0x7 // bits 12-14 for func3 + { + 0b000 => + match (instruction >> 30) & 0x1 // bit 30 of funct5 component in RType + { + 0b0 => Instruction::RType(RInstruction::Add), + 0b1 => Instruction::RType(RInstruction::Sub), + _ => panic!("Unknown RType in add/sub instructions with full inst {}", instruction), + }, + 0b001 => Instruction::RType(RInstruction::ShiftLeftLogical), + 0b010 => Instruction::RType(RInstruction::SetLessThan), + 0b011 => Instruction::RType(RInstruction::SetLessThanUnsigned), + 0b100 => Instruction::RType(RInstruction::Xor), + 0b101 => + match (instruction >> 30) & 0x1 // bit 30 of funct5 component in RType + { + 0b0 => Instruction::RType(RInstruction::ShiftRightLogical), + 0b1 => Instruction::RType(RInstruction::ShiftRightArithmetic), + _ => panic!("Unknown RType in shift right instructions with full inst {}", instruction), + }, + 0b110 => Instruction::RType(RInstruction::Or), + 0b111 => Instruction::RType(RInstruction::And), + _ => panic!("Unknown RType 0110011 instruction with full inst {}", instruction), + }, + 0b0001111 => + match (instruction >> 12) & 0x7 // bits 12-14 for func3 + { + 0b000 => Instruction::RType(RInstruction::Fence), + 0b001 => Instruction::RType(RInstruction::FenceI), + _ => panic!("Unknown RType 0001111 (Fence) instruction with full inst {}", instruction), + }, + // FIXME: we should implement more syscalls here, and check the register state. + // Even better, only one constructor call ecall, and in the + // interpreter, we do the action depending on it + 0b1110011 => Instruction::SyscallType(SyscallInstruction::SyscallSuccess), + _ => panic!("Unknown instruction with full inst {:b}, and opcode {:b}", instruction, instruction & 0b1111111), + } + }; + // display the opcode + println!( + "Decoded instruction {:?} with opcode {:?}", + instruction, opcode + ); + (opcode, instruction) + } + pub fn reset_scratch_state(&mut self) { self.scratch_state_idx = 0; self.scratch_state = fresh_scratch_state(); From 7e6756fc42c91a4824faab27845b528fce5ec1b8 Mon Sep 17 00:00:00 2001 From: svv232 Date: Tue, 12 Nov 2024 00:08:16 -0500 Subject: [PATCH 3/6] correcting more instruction implementations in display --- o1vm/src/interpreters/riscv32i/interpreter.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/o1vm/src/interpreters/riscv32i/interpreter.rs b/o1vm/src/interpreters/riscv32i/interpreter.rs index 7d4c7dd6d4..f528cbf948 100644 --- a/o1vm/src/interpreters/riscv32i/interpreter.rs +++ b/o1vm/src/interpreters/riscv32i/interpreter.rs @@ -78,10 +78,10 @@ pub enum SInstruction { pub enum SBInstruction { #[default] BranchEq, // beq - BranchNeq, // bne - BranchLessThan, // blt - BranchGreaterThanEqual, // bge - BranchLessThanUnsigned, // bltu + BranchNeq, // bne + BranchLessThan, // blt + BranchGreaterThanEqual, // bge + BranchLessThanUnsigned, // bltu BranchGreaterThanEqualUnsigned, // bgeu } @@ -242,7 +242,7 @@ impl std::fmt::Display for SBInstruction { SBInstruction::BranchEq => write!(f, "beq"), SBInstruction::BranchNeq => write!(f, "bne"), SBInstruction::BranchLessThan => write!(f, "blt"), - SBInstruction::BranchGreaterThanEqual=> write!(f, "bge"), + SBInstruction::BranchGreaterThanEqual => write!(f, "bge"), SBInstruction::BranchLessThanUnsigned => write!(f, "bltu"), SBInstruction::BranchGreaterThanEqualUnsigned => write!(f, "bgeu"), } From 359d5659030aa695ec675462ece9580fbd526884 Mon Sep 17 00:00:00 2001 From: svv232 Date: Tue, 12 Nov 2024 00:08:41 -0500 Subject: [PATCH 4/6] adding step function and test_no_action test --- o1vm/src/interpreters/riscv32i/witness.rs | 22 ++++++++++++++- o1vm/tests/test_riscv_elf.rs | 34 ++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/o1vm/src/interpreters/riscv32i/witness.rs b/o1vm/src/interpreters/riscv32i/witness.rs index 0b607d337a..aabb80e30f 100644 --- a/o1vm/src/interpreters/riscv32i/witness.rs +++ b/o1vm/src/interpreters/riscv32i/witness.rs @@ -3,7 +3,7 @@ use super::{ column::Column, interpreter::{ - IInstruction, Instruction, InterpreterEnv, RInstruction, SBInstruction, SInstruction, + self, IInstruction, Instruction, InterpreterEnv, RInstruction, SBInstruction, SInstruction, SyscallInstruction, UInstruction, UJInstruction, }, registers::Registers, @@ -745,6 +745,26 @@ impl Env { (opcode, instruction) } + /// Execute a single step in the RISCV32i program + pub fn step(&mut self) -> Instruction { + self.reset_scratch_state(); + let (opcode, _instruction) = self.decode_instruction(); + + interpreter::interpret_instruction(self, opcode); + + self.instruction_counter = self.next_instruction_counter(); + + // Integer division by MAX_ACC to obtain the actual instruction count + if self.halt { + println!( + "Halted at step={} instruction={:?}", + self.normalized_instruction_counter(), + opcode + ); + } + opcode + } + pub fn reset_scratch_state(&mut self) { self.scratch_state_idx = 0; self.scratch_state = fresh_scratch_state(); diff --git a/o1vm/tests/test_riscv_elf.rs b/o1vm/tests/test_riscv_elf.rs index 64ef95477a..f6cf008924 100644 --- a/o1vm/tests/test_riscv_elf.rs +++ b/o1vm/tests/test_riscv_elf.rs @@ -1,4 +1,9 @@ -use o1vm::interpreters::riscv32i::interpreter::{IInstruction, Instruction, RInstruction}; +use mina_curves::pasta::Fp; +use o1vm::interpreters::riscv32i::{ + interpreter::{IInstruction, Instruction, RInstruction}, + witness::Env, + PAGE_SIZE, +}; #[test] // Checking an instruction can be converted into a string. @@ -17,3 +22,30 @@ fn test_instruction_can_be_converted_into_string() { let instruction = Instruction::IType(IInstruction::LoadHalf); assert_eq!(instruction.to_string(), "lh"); } + +#[test] +fn test_no_action() { + let curr_dir = std::env::current_dir().unwrap(); + let path = curr_dir.join(std::path::PathBuf::from( + "resources/programs/riscv32i/no-action", + )); + let state = o1vm::elf_loader::parse_riscv32i(&path).unwrap(); + let mut witness = Env::::create(PAGE_SIZE.try_into().unwrap(), state); + // This is the output we get by running objdump -d no-action + assert_eq!(witness.registers.current_instruction_pointer, 69844); + assert_eq!(witness.registers.next_instruction_pointer, 69848); + + (0..=7).for_each(|_| { + let instr = witness.step(); + // li is addi, li is a pseudo instruction + assert_eq!(instr, Instruction::IType(IInstruction::AddImmediate)) + }); + assert_eq!(witness.registers.general_purpose[10], 0); + assert_eq!(witness.registers.general_purpose[11], 0); + assert_eq!(witness.registers.general_purpose[12], 0); + assert_eq!(witness.registers.general_purpose[13], 0); + assert_eq!(witness.registers.general_purpose[14], 0); + assert_eq!(witness.registers.general_purpose[15], 0); + assert_eq!(witness.registers.general_purpose[16], 0); + assert_eq!(witness.registers.general_purpose[17], 42); +} From c94c9f213ab05c15600b6ecd2ef2375572f10506 Mon Sep 17 00:00:00 2001 From: svv232 Date: Tue, 12 Nov 2024 00:17:55 -0500 Subject: [PATCH 5/6] fixing imports and removing tests so o1vmtest can be imported in the next sequence of changes --- o1vm/tests/test_riscv_elf.rs | 37 +++--------------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/o1vm/tests/test_riscv_elf.rs b/o1vm/tests/test_riscv_elf.rs index f6cf008924..6d0b39ad14 100644 --- a/o1vm/tests/test_riscv_elf.rs +++ b/o1vm/tests/test_riscv_elf.rs @@ -1,9 +1,5 @@ -use mina_curves::pasta::Fp; -use o1vm::interpreters::riscv32i::{ - interpreter::{IInstruction, Instruction, RInstruction}, - witness::Env, - PAGE_SIZE, -}; +use o1vm::interpreters::riscv32i:: + interpreter::{IInstruction, Instruction, RInstruction}; #[test] // Checking an instruction can be converted into a string. @@ -21,31 +17,4 @@ fn test_instruction_can_be_converted_into_string() { let instruction = Instruction::IType(IInstruction::LoadHalf); assert_eq!(instruction.to_string(), "lh"); -} - -#[test] -fn test_no_action() { - let curr_dir = std::env::current_dir().unwrap(); - let path = curr_dir.join(std::path::PathBuf::from( - "resources/programs/riscv32i/no-action", - )); - let state = o1vm::elf_loader::parse_riscv32i(&path).unwrap(); - let mut witness = Env::::create(PAGE_SIZE.try_into().unwrap(), state); - // This is the output we get by running objdump -d no-action - assert_eq!(witness.registers.current_instruction_pointer, 69844); - assert_eq!(witness.registers.next_instruction_pointer, 69848); - - (0..=7).for_each(|_| { - let instr = witness.step(); - // li is addi, li is a pseudo instruction - assert_eq!(instr, Instruction::IType(IInstruction::AddImmediate)) - }); - assert_eq!(witness.registers.general_purpose[10], 0); - assert_eq!(witness.registers.general_purpose[11], 0); - assert_eq!(witness.registers.general_purpose[12], 0); - assert_eq!(witness.registers.general_purpose[13], 0); - assert_eq!(witness.registers.general_purpose[14], 0); - assert_eq!(witness.registers.general_purpose[15], 0); - assert_eq!(witness.registers.general_purpose[16], 0); - assert_eq!(witness.registers.general_purpose[17], 42); -} +} \ No newline at end of file From 0681bf062a9ec62a5e0687ea8c12b4d6efe16de5 Mon Sep 17 00:00:00 2001 From: svv232 Date: Tue, 12 Nov 2024 01:30:34 -0500 Subject: [PATCH 6/6] reformat import in test --- o1vm/tests/test_riscv_elf.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/o1vm/tests/test_riscv_elf.rs b/o1vm/tests/test_riscv_elf.rs index 6d0b39ad14..64ef95477a 100644 --- a/o1vm/tests/test_riscv_elf.rs +++ b/o1vm/tests/test_riscv_elf.rs @@ -1,5 +1,4 @@ -use o1vm::interpreters::riscv32i:: - interpreter::{IInstruction, Instruction, RInstruction}; +use o1vm::interpreters::riscv32i::interpreter::{IInstruction, Instruction, RInstruction}; #[test] // Checking an instruction can be converted into a string. @@ -17,4 +16,4 @@ fn test_instruction_can_be_converted_into_string() { let instruction = Instruction::IType(IInstruction::LoadHalf); assert_eq!(instruction.to_string(), "lh"); -} \ No newline at end of file +}