diff --git a/assembler/grammar/assembly.pest b/assembler/grammar/assembly.pest index 530aa40..82cea9e 100644 --- a/assembler/grammar/assembly.pest +++ b/assembler/grammar/assembly.pest @@ -3,7 +3,7 @@ comment = { ";" ~ (!"\n" ~ ANY)* ~ "\n" } label = { (!":" ~ !"\n" ~ ANY)+ ~ ":" ~ "\n" } instruction = { mnemonic ~ (operand ~ ", "?)+? ~ "\n"? } mnemonic = { - "lw" | "sw" | "jalv" | "jal" | "beqi" | "beq" | "bnei" | "bne" | "imm32" | "stop" | + "lw" | "sw" | "loadu8" | "loads8" | "storeu8" | "jalv" | "jal" | "beqi" | "beq" | "bnei" | "bne" | "imm32" | "stop" | "advread" | "advwrite" | "addi" | "add" | "subi" | "sub" | "muli" | "mul" | "mulhsi"| "mulhui"| "mulhs"| "mulhu" | "divi" | "div" | "sdiv" | "sdivi" | "ilte" | "ltei" | "lte" | "ilt" | "lti" | "lt" | "shli" | "shl" | "shri" | "shr" | "srai" | "sra" | diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs index f24e48f..b3ed64b 100644 --- a/assembler/src/lib.rs +++ b/assembler/src/lib.rs @@ -58,7 +58,10 @@ pub fn assemble(input: &str) -> Result, String> { let opcode = match mnemonic { // Core CPU "lw" => LOAD32, + "loadu8" => LOADU8, + "loads8" => LOADS8, "sw" => STORE32, + "storeu8" => STOREU8, "jal" => JAL, "jalv" => JALV, "beq" | "beqi" => BEQ, @@ -101,12 +104,12 @@ pub fn assemble(input: &str) -> Result, String> { // Insert zero operands if necessary match mnemonic { - "lw" => { + "lw" | "loadu8" | "loads8" => { // (a, 0, c, 0, 0) operands.insert(1, 0); operands.extend(vec![0; 2]); } - "sw" => { + "sw" | "storeu8" => { // (0, b, c, 0, 0) operands.insert(0, 0); operands.extend(vec![0; 2]); diff --git a/basic/src/bin/valida.rs b/basic/src/bin/valida.rs index 71aa70e..c59b45e 100644 --- a/basic/src/bin/valida.rs +++ b/basic/src/bin/valida.rs @@ -166,7 +166,7 @@ fn last_frame(_: ArgMatches, context: &mut Context) -> Result> { frame += &format!("Current FP: 0x{:x}\n", fp).as_str(); // print last frame - for i in (-5..(last_size / 4) + 1).rev() { + for i in (-10..(last_size / 4) + 1).rev() { let offset = (i * 4) as i32; let read_addr = (fp + offset) as u32; let string_val = context.machine_.mem().examine(read_addr); diff --git a/basic/src/lib.rs b/basic/src/lib.rs index 55fea07..67fe80f 100644 --- a/basic/src/lib.rs +++ b/basic/src/lib.rs @@ -37,8 +37,8 @@ use valida_bus::{ }; use valida_cpu::{ BeqInstruction, BneInstruction, Imm32Instruction, JalInstruction, JalvInstruction, - Load32Instruction, LoadFpInstruction, ReadAdviceInstruction, StopInstruction, - Store32Instruction, + Load32Instruction, LoadFpInstruction, LoadS8Instruction, LoadU8Instruction, + ReadAdviceInstruction, StopInstruction, Store32Instruction, StoreU8Instruction, }; use valida_cpu::{CpuChip, MachineWithCpuChip}; use valida_machine::__internal::p3_challenger::{CanObserve, FieldChallenger}; @@ -63,7 +63,11 @@ use valida_machine::StarkConfig; pub struct BasicMachine { // Core instructions load32: Load32Instruction, + loadu8: LoadU8Instruction, + loads8: LoadS8Instruction, store32: Store32Instruction, + storeu8: StoreU8Instruction, + jal: JalInstruction, jalv: JalvInstruction, beq: BeqInstruction, @@ -1071,9 +1075,18 @@ impl Machine for BasicMachine { >::OPCODE => { Load32Instruction::execute_with_advice::(self, ops, advice) } + >::OPCODE => { + LoadU8Instruction::execute_with_advice::(self, ops, advice) + } + >::OPCODE => { + LoadS8Instruction::execute_with_advice::(self, ops, advice) + } >::OPCODE => { Store32Instruction::execute_with_advice::(self, ops, advice) } + >::OPCODE => { + StoreU8Instruction::execute_with_advice::(self, ops, advice) + } >::OPCODE => { JalInstruction::execute_with_advice::(self, ops, advice) } diff --git a/cpu/src/columns.rs b/cpu/src/columns.rs index b19bb1f..589cb99 100644 --- a/cpu/src/columns.rs +++ b/cpu/src/columns.rs @@ -48,7 +48,10 @@ pub struct OpcodeFlagCols { pub is_bus_op_with_mem: T, pub is_imm_op: T, pub is_load: T, + pub is_load_u8: T, + pub is_load_s8: T, pub is_store: T, + pub is_store_u8: T, pub is_beq: T, pub is_bne: T, pub is_jal: T, diff --git a/cpu/src/lib.rs b/cpu/src/lib.rs index 2273f2c..ded7772 100644 --- a/cpu/src/lib.rs +++ b/cpu/src/lib.rs @@ -11,11 +11,13 @@ use core::marker::Sync; use core::mem::transmute; use valida_bus::{MachineWithGeneralBus, MachineWithMemBus, MachineWithProgramBus}; use valida_machine::{ - instructions, AdviceProvider, Chip, Instruction, InstructionWord, Interaction, Operands, Word, + addr_of_word, index_of_byte, instructions, AdviceProvider, Chip, Instruction, InstructionWord, + Interaction, Operands, Word, }; use valida_memory::{MachineWithMemoryChip, Operation as MemoryOperation}; use valida_opcodes::{ - BEQ, BNE, BYTES_PER_INSTR, IMM32, JAL, JALV, LOAD32, LOADFP, READ_ADVICE, STOP, STORE32, + BEQ, BNE, BYTES_PER_INSTR, IMM32, JAL, JALV, LOAD32, LOADFP, LOADS8, LOADU8, READ_ADVICE, STOP, + STORE32, STOREU8, }; use p3_air::VirtualPairCol; @@ -31,7 +33,10 @@ pub mod stark; #[derive(Clone)] pub enum Operation { Store32, + StoreU8, Load32, + LoadU8, + LoadS8, Jal, Jalv, Beq(Option> /*imm*/), @@ -175,6 +180,15 @@ impl CpuChip { Operation::Load32 => { cols.opcode_flags.is_load = SC::Val::one(); } + Operation::StoreU8 => { + cols.opcode_flags.is_store_u8 = SC::Val::one(); + } + Operation::LoadU8 => { + cols.opcode_flags.is_load_u8 = SC::Val::one(); + } + Operation::LoadS8 => { + cols.opcode_flags.is_load_s8 = SC::Val::one(); + } Operation::Jal => { cols.opcode_flags.is_jal = SC::Val::one(); } @@ -349,7 +363,10 @@ pub trait MachineWithCpuChip: MachineWithMemoryChip { instructions!( Load32Instruction, + LoadU8Instruction, + LoadS8Instruction, Store32Instruction, + StoreU8Instruction, JalInstruction, JalvInstruction, BeqInstruction, @@ -441,6 +458,119 @@ where } } +impl Instruction for LoadU8Instruction +where + M: MachineWithCpuChip, + F: Field, +{ + const OPCODE: u32 = LOADU8; + + fn execute(state: &mut M, ops: Operands) { + let opcode = >::OPCODE; + let clk = state.cpu().clock; + let pc = state.cpu().pc; + let fp = state.cpu().fp; + + let read_addr_loc = (fp as i32 + ops.c()) as u32; + + let read_addr = state + .mem_mut() + .read(clk, read_addr_loc, true, pc, opcode, 0, ""); + let read_addr_index = addr_of_word(read_addr.into()); + + // The word from the read address. + let cell = state.mem_mut().read( + clk, + read_addr_index, + true, + pc, + opcode, + 1, + &format!( + "fp = {}, c = {}, [fp+c] = {:?}", + fp as i32, + ops.c() as u32, + read_addr_index + ), + ); + + // The array index of the word for the byte to read from + let index_of_read = index_of_byte(read_addr.into()); + // The byte from the read cell. + let cell_byte = cell[index_of_read]; + + let write_addr = (state.cpu().fp as i32 + ops.a()) as u32; + // The address, converted to a multiple of 4. + let write_addr_index = addr_of_word(write_addr); + + // The Word to write, with one byte overwritten to the read byte + let cell_to_write = Word::zero_extend_byte(cell_byte); + + state + .mem_mut() + .write(clk, write_addr_index, cell_to_write, true); + state.cpu_mut().pc += 1; + state.cpu_mut().push_op(Operation::LoadU8, opcode, ops); + } +} + +impl Instruction for LoadS8Instruction +where + M: MachineWithCpuChip, + F: Field, +{ + const OPCODE: u32 = LOADS8; + + fn execute(state: &mut M, ops: Operands) { + let opcode = >::OPCODE; + let clk = state.cpu().clock; + let pc = state.cpu().pc; + let fp = state.cpu().fp; + + let read_addr_loc = (fp as i32 + ops.c()) as u32; + + let read_addr = state + .mem_mut() + .read(clk, read_addr_loc, true, pc, opcode, 0, ""); + + let read_addr_index = addr_of_word(read_addr.into()); + + // The word from the read address. + let cell = state.mem_mut().read( + clk, + read_addr_index, + true, + pc, + opcode, + 1, + &format!( + "fp = {}, c = {}, [fp+c] = {:?}", + fp as i32, + ops.c() as u32, + read_addr_index + ), + ); + + // The array index of the word for the byte to read from + let index_of_read = index_of_byte(read_addr.into()); + // The byte from the read cell. + let cell_byte = cell[index_of_read]; + + let write_addr = (state.cpu().fp as i32 + ops.a()) as u32; + // The address, converted to a multiple of 4. + let write_addr_index = addr_of_word(write_addr); + + // The Word to write, with one byte overwritten to the read byte + let cell_to_write = Word::sign_extend_byte(cell_byte); + + state + .mem_mut() + .write(clk, write_addr_index, cell_to_write, true); + state.cpu_mut().pc += 1; + state.cpu_mut().push_op(Operation::LoadS8, opcode, ops); + } +} + impl Instruction for Store32Instruction where M: MachineWithCpuChip, @@ -456,7 +586,7 @@ where let pc = state.cpu().pc; let write_addr = state .mem_mut() - .read(clk, write_addr_loc.into(), true, pc, opcode, 0, ""); + .read(clk, write_addr_loc, true, pc, opcode, 0, ""); let cell = state .mem_mut() .read(clk, read_addr, true, pc, opcode, 1, ""); @@ -466,6 +596,59 @@ where } } +impl Instruction for StoreU8Instruction +where + M: MachineWithCpuChip, + F: Field, +{ + const OPCODE: u32 = STOREU8; + + fn execute(state: &mut M, ops: Operands) { + let opcode = >::OPCODE; + let clk = state.cpu().clock; + let read_addr = (state.cpu().fp as i32 + ops.c()) as u32; + + // Make sure we get to the correct and non empty map for the byte. + let read_addr_index = addr_of_word(read_addr); + let write_addr_loc = (state.cpu().fp as i32 + ops.b()) as u32; + let pc = state.cpu().pc; + let write_addr = state + .mem_mut() + .read(clk, write_addr_loc.into(), true, pc, opcode, 0, ""); + + // Read the cell from the read address. + let cell = state + .mem_mut() + .read(clk, read_addr_index, true, pc, opcode, 1, ""); + + // The array index of the word for the byte to read from + let index_of_read = index_of_byte(read_addr); + + // The word from the read address. + let cell_read = cell.0; + // The byte from the read cell. + let cell_byte = cell_read[index_of_read]; + + // The array index of the word for the byte to write to + let index_of_write = index_of_byte(write_addr.into()); + + // The key to the memory map, converted to a multiple of 4. + let write_addr_index = addr_of_word(write_addr.into()); + + // The original content of the cell to write to. If the cell is empty, initiate it with a default value. + let cell_write = state.mem_mut().read_or_init(clk, write_addr_index, true); + + // The Word to write, with one byte overwritten to the read byte + let cell_to_write = cell_write.update_byte(cell_byte, index_of_write); + + state + .mem_mut() + .write(clk, write_addr_index, cell_to_write, true); + state.cpu_mut().pc += 1; + state.cpu_mut().push_op(Operation::StoreU8, opcode, ops); + } +} + impl Instruction for JalInstruction where M: MachineWithCpuChip, diff --git a/machine/src/core.rs b/machine/src/core.rs index e37aabb..4ff8d2f 100644 --- a/machine/src/core.rs +++ b/machine/src/core.rs @@ -7,6 +7,18 @@ use p3_field::{Field, PrimeField}; #[derive(Copy, Clone, Debug, Default)] pub struct Word(pub [F; MEMORY_CELL_BYTES]); +// Functions for byte manipulations +/// Get the index of a byte in a memory cell. +pub fn index_of_byte(addr: u32) -> usize { + (addr & 3) as usize +} + +/// Get the address of the memory cells which is not empty (a multiple of 4). +pub fn addr_of_word(addr: u32) -> u32 { + (addr & !3) as u32 +} +//---------------------------------- + impl Word { pub fn from_u8(byte: u8) -> Self { let mut result = [0; MEMORY_CELL_BYTES]; @@ -15,6 +27,31 @@ impl Word { } } +impl Word { + pub fn sign_extend_byte(byte: u8) -> Self { + let sign = byte as i8 >> 7; + let mut result: [u8; MEMORY_CELL_BYTES] = [sign as u8; MEMORY_CELL_BYTES]; + result[0] = byte; + Self(result) + } +} + +impl Word { + pub fn zero_extend_byte(byte: u8) -> Self { + let mut result: [u8; MEMORY_CELL_BYTES] = [0; MEMORY_CELL_BYTES]; + result[0] = byte; + Self(result) + } +} + +impl Word { + pub fn update_byte(self, byte: u8, loc: usize) -> Self { + let mut result: [u8; MEMORY_CELL_BYTES] = self.0; + result[loc] = byte; + Self(result) + } +} + impl Word { pub fn transform(self, mut f: G) -> Word where diff --git a/machine/src/program.rs b/machine/src/program.rs index f61bcba..3032f8c 100644 --- a/machine/src/program.rs +++ b/machine/src/program.rs @@ -102,9 +102,18 @@ impl InstructionWord { valida_opcodes::LOAD32 => { format!("{}(fp), {}(fp)", self.operands.0[0], self.operands.0[2]) } + valida_opcodes::LOADU8 => { + format!("{}(fp), {}(fp)", self.operands.0[0], self.operands.0[2]) + } + valida_opcodes::LOADS8 => { + format!("{}(fp), {}(fp)", self.operands.0[0], self.operands.0[2]) + } valida_opcodes::STORE32 => { format!("{}(fp), {}(fp)", self.operands.0[1], self.operands.0[2]) } + valida_opcodes::STOREU8 => { + format!("{}(fp), {}(fp)", self.operands.0[1], self.operands.0[2]) + } _ => { format!( "{}(fp), {}, {}", diff --git a/memory/src/lib.rs b/memory/src/lib.rs index 0c9725a..1bb80ea 100644 --- a/memory/src/lib.rs +++ b/memory/src/lib.rs @@ -2,6 +2,8 @@ extern crate alloc; +use valida_machine::MEMORY_CELL_BYTES; + use crate::alloc::string::ToString; use crate::columns::{MemoryCols, MEM_COL_MAP, NUM_MEM_COLS}; use alloc::collections::BTreeMap; @@ -79,6 +81,7 @@ impl MemoryChip { } } + /// Read from a cell. If the cell is empty, panic. pub fn read( &mut self, clk: u32, @@ -100,6 +103,22 @@ impl MemoryChip { value } + /// Read from a cell. If the cell is empty, initialize it with the default values. + pub fn read_or_init(&mut self, clk: u32, address: u32, log: bool) -> Word { + let value = self + .cells + .get(&address.into()) + .copied() + .unwrap_or_else(|| Word([0; MEMORY_CELL_BYTES])); + if log { + self.operations + .entry(clk) + .or_insert_with(Vec::new) + .push(Operation::Read(address.into(), value)); + } + value + } + pub fn write(&mut self, clk: u32, address: u32, value: Word, log: bool) { if log { self.operations diff --git a/opcodes/src/lib.rs b/opcodes/src/lib.rs index 161be3f..5ffd910 100644 --- a/opcodes/src/lib.rs +++ b/opcodes/src/lib.rs @@ -15,6 +15,10 @@ pub enum Opcode { STOP = 8, READ_ADVICE = 9, LOADFP = 10, + LOADU8 = 11, + LOADS8 = 12, + STOREU8 = 13, + ADD32 = 100, SUB32 = 101, MUL32 = 102, @@ -56,6 +60,9 @@ declare_opcode!(BNE); declare_opcode!(IMM32); declare_opcode!(STOP); declare_opcode!(LOADFP); +declare_opcode!(LOADU8); +declare_opcode!(LOADS8); +declare_opcode!(STOREU8); /// NONDETERMINISTIC declare_opcode!(READ_ADVICE);