diff --git a/Cargo.lock b/Cargo.lock index 20674aca..0e893d9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2378,10 +2378,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "zisk-simulator" -version = "0.1.0" - [[package]] name = "zisk-wc" version = "0.1.0" @@ -2400,3 +2396,10 @@ dependencies = [ [[package]] name = "ziskos" version = "0.1.0" + +[[package]] +name = "zisksim" +version = "0.1.0" +dependencies = [ + "riscv2zisk", +] diff --git a/simulator/Cargo.toml b/simulator/Cargo.toml index cd466ff3..e8d6c6bf 100644 --- a/simulator/Cargo.toml +++ b/simulator/Cargo.toml @@ -1,6 +1,15 @@ [package] -name = "zisk-simulator" +name = "zisksim" version = "0.1.0" edition = "2021" [dependencies] +riscv2zisk = {path="../riscv/riscv2zisk"} + +[lib] +name = "zisksim" +path = "src/lib.rs" + +[[bin]] +name = "zisksim" +path = "src/bin/zisksim.rs" \ No newline at end of file diff --git a/simulator/src/bin/zisksim.rs b/simulator/src/bin/zisksim.rs new file mode 100644 index 00000000..9c1b789a --- /dev/null +++ b/simulator/src/bin/zisksim.rs @@ -0,0 +1,123 @@ +use riscv2zisk::Riscv2zisk; +use std::{ + env, fs, + fs::metadata, + path::{Path, PathBuf}, + process, +}; +use zisksim::{Sim, SimOptions}; + +fn _list_files(vec: &mut Vec, path: &Path) { + if metadata(path).unwrap().is_dir() { + let paths = fs::read_dir(path).unwrap(); + for path_result in paths { + let full_path = path_result.unwrap().path(); + if metadata(&full_path).unwrap().is_dir() { + _list_files(vec, &full_path); + } else { + vec.push(full_path); + } + } + } +} + +fn list_files(path: &Path) -> Vec { + let mut vec = Vec::new(); + _list_files(&mut vec, path); + vec +} + +fn main() { + //println!("zisk_tester converts an ELF RISCV file into a ZISK ASM program, simulates it, and + // copies the output to console"); + + // Get program arguments + let args: Vec = env::args().collect(); + + // Check program arguments length + if args.len() != 2 { + eprintln!("Error parsing arguments: number of arguments should be 1. Usage: zisk_tester "); + process::exit(1); + } + + let argument = &args[1]; + let mut multiple_files = false; + let md = metadata(argument).unwrap(); + let mut elf_files: Vec = Vec::new(); + if md.is_file() { + elf_files.push(argument.clone()); + } else if md.is_dir() { + multiple_files = true; + let path = Path::new(argument); + let files = list_files(path); + for file in files { + let file_name = file.display().to_string(); + if file_name.contains("dut") && file_name.ends_with(".elf") { + elf_files.push(file_name.to_string().clone()); + println!("found DUT ELF file: {}", file_name); + } + } + } + + let elf_files_len = elf_files.len(); + + if multiple_files { + println!("Going to process {} ELF files", elf_files_len); + } + + //const FIRST_ELF_FILE: u64 = 0; + + for (elf_file_counter, elf_file) in elf_files.into_iter().enumerate() { + // Get the input parameters: ELF (RISCV) file name (input data) + //let elf_file = args[1].clone(); + let zisk_file = String::new(); + + if multiple_files { + println!("ELF file {}/{}: {}", elf_file_counter, elf_files_len, elf_file); + } + /*if (FIRST_ELF_FILE > 0) && (elf_file_counter < FIRST_ELF_FILE) { + println!("Skipping file {}", elf_file); + continue; + }*/ + + // Create an instance of the program converter + let rv2zk = Riscv2zisk::new(elf_file, zisk_file); + + // Convert program to rom + let result = rv2zk.run(); + if result.is_err() { + println!("Application error: {}", result.err().unwrap()); + process::exit(1); + } + let rom = result.unwrap(); + + // Create an empty input + let input: Vec = Vec::new(); + + // Create a simulator instance with this rom and input + let mut sim = Sim::new(rom, input); + + // Create a simulator options instance with the default values + let sim_options = SimOptions::new(); + + // Run the simulations + sim.run(sim_options); + if !sim.terminated() { + println!("Simulation did not complete"); + process::exit(1); + } + + if !multiple_files { + // Get the simulation outpus as a u32 vector + let output = sim.get_output_32(); + + // Log the output in console + for o in &output { + println!("{:08x}", o); + } + } + } + + // Return successfully + process::exit(0); +} diff --git a/simulator/src/lib.rs b/simulator/src/lib.rs index 8b137891..7a4b8027 100644 --- a/simulator/src/lib.rs +++ b/simulator/src/lib.rs @@ -1 +1,11 @@ +mod mem; +pub use mem::*; +mod mem_section; +pub use mem_section::*; +mod sim; +pub use sim::*; +mod sim_context; +pub use sim_context::*; +mod sim_options; +pub use sim_options::*; diff --git a/simulator/src/mem.rs b/simulator/src/mem.rs new file mode 100644 index 00000000..60b245ce --- /dev/null +++ b/simulator/src/mem.rs @@ -0,0 +1,129 @@ +use crate::MemSection; +use riscv2zisk::{read_u16_le, read_u32_le, read_u64_le, write_u16_le, write_u32_le, write_u64_le}; + +/// Memory structure, containing several read sections and one single write section +pub struct Mem { + pub read_sections: Vec, + pub write_section: MemSection, +} + +/// Default constructor for Mem structure +impl Default for Mem { + fn default() -> Self { + Self::new() + } +} + +/// Memory structure implementation +impl Mem { + /// Memory structue constructor + pub fn new() -> Mem { + Mem { read_sections: Vec::new(), write_section: MemSection::new() } + } + + /// Adds a read section to the memory structure + pub fn add_read_section(&mut self, start: u64, buffer: &[u8]) { + let mem_section = + MemSection { start, end: start + buffer.len() as u64, buffer: buffer.to_owned() }; + self.read_sections.push(mem_section); + } + + /// Adds a write section to the memory structure, which cannot be written twice + pub fn add_write_section(&mut self, start: u64, size: u64) { + //println!("Mem::add_write_section() start={} size={}", start, size); + + // Check the start address is not zero + if start == 0 { + panic!("add_write_section() got invalid start={}", start); + } + + // Check the write section address has been set before this call + if self.write_section.start != 0 { + panic!( + "add_write_section() only one write section allowed, write_section.start={}", + self.write_section.start + ); + } + + // Create an empty vector of size bytes + let mem: Vec = vec![0; size as usize]; + + // Store as the write section + self.write_section.start = start; + self.write_section.end = start + mem.len() as u64; + self.write_section.buffer = mem; + } + + /// Read a u64 value from the memory read sections, based on the provided address and width + pub fn read(&self, addr: u64, width: u64) -> u64 { + // First try to read in the write section + if (addr >= self.write_section.start) && (addr <= (self.write_section.end - width)) { + // Calculate the read position + let read_position: usize = (addr - self.write_section.start) as usize; + + // Read the requested data based on the provided width + let value: u64 = match width { + 1 => self.write_section.buffer[read_position] as u64, + 2 => read_u16_le(&self.write_section.buffer, read_position) as u64, + 4 => read_u32_le(&self.write_section.buffer, read_position) as u64, + 8 => read_u64_le(&self.write_section.buffer, read_position), + _ => panic!("Mem::read() invalid width={}", width), + }; + + //println!("Mem::read() addr={:x} width={} value={:x}={}", addr, width, value, value); + return value; + } + + // For all read sections + for i in 0..self.read_sections.len() { + // Get a section reference + let section = &self.read_sections[i]; + + // If the provided address and size are between this section address range, then we + // found the section + if (addr >= section.start) && (addr <= (section.end - width)) { + // Calculate the read position + let read_position: usize = (addr - section.start) as usize; + + // Read the requested data based on the provided width + let value: u64 = match width { + 1 => section.buffer[read_position] as u64, + 2 => read_u16_le(§ion.buffer, read_position) as u64, + 4 => read_u32_le(§ion.buffer, read_position) as u64, + 8 => read_u64_le(§ion.buffer, read_position), + _ => panic!("Mem::read() invalid width={}", width), + }; + + //println!("Mem::read() addr={:x} width={} value={:x}={}", addr, width, value, + // value); + return value; + } + } + panic!("Read out of Range: 0x{:08}", addr); + } + + /// Write a u64 value to the memory write section, based on the provided address and width + pub fn write(&mut self, addr: u64, val: u64, width: u64) { + //println!("Mem::write() addr={:x} width={} value={:x}={}", addr, width, val, val); + + // Get a reference to the write section + let section = &mut self.write_section; + + // Check that the address and width fall into this section address range + if (addr < section.start) || ((addr + width) > section.end) { + panic!("Mem::write() invalid addr={}", addr); + } + + // Calculate the write position + let write_position: usize = (addr - section.start) as usize; + + // Write the value based on the provided width + match width { + 1 => section.buffer[write_position] = (val & 0xFF) as u8, + 2 => write_u16_le(&mut section.buffer, write_position, (val & 0xFFFF) as u16), + 4 => write_u32_le(&mut section.buffer, write_position, (val & 0xFFFFFFFF) as u32), + 8 => write_u64_le(&mut section.buffer, write_position, val), + _ => panic!("Mem::write() invalid width={}", width), + } + } +} diff --git a/simulator/src/mem_section.rs b/simulator/src/mem_section.rs new file mode 100644 index 00000000..50803f90 --- /dev/null +++ b/simulator/src/mem_section.rs @@ -0,0 +1,21 @@ +/// Memory section data, including a buffer vector, and start and end addresses +pub struct MemSection { + pub start: u64, + pub end: u64, + pub buffer: Vec, +} + +/// Default constructor for MemSection structure +impl Default for MemSection { + fn default() -> Self { + Self::new() + } +} + +/// Memory section structure implementation +impl MemSection { + /// Memory section constructor + pub fn new() -> MemSection { + MemSection { start: 0, end: 0, buffer: Vec::new() } + } +} diff --git a/simulator/src/sim.rs b/simulator/src/sim.rs new file mode 100644 index 00000000..bf891abb --- /dev/null +++ b/simulator/src/sim.rs @@ -0,0 +1,293 @@ +use crate::{SimContext, SimOptions}; +use riscv2zisk::{ + ZiskOperations, ZiskRom, OUTPUT_ADDR, SRC_C, SRC_IMM, SRC_IND, SRC_MEM, SRC_SP, SRC_STEP, + STORE_IND, STORE_MEM, STORE_NONE, SYS_ADDR, +}; +use std::collections::HashMap; + +/// Human-readable names of the 32 well-known RISCV registers, to be used in traces +const REG_NAMES: [&str; 32] = [ + "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", + "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", + "t5", "t6", +]; + +/// ZisK simulator structure, containing the ZisK rom, the list of ZisK operations, and the +/// execution context +pub struct Sim { + /// ZisK rom, containing the program to execute, which is constant for this program except for + /// the input data + pub rom: ZiskRom, + + /// ZisK operations (c, flag) = f(a, b), one per supported opcode + operations: ZiskOperations, + + /// Context, where the state of the execution is stored and modified at every execution step + ctx: SimContext, +} + +/// ZisK simulator structure implementation +impl Sim { + //// ZisK simulator structure constructor + pub fn new(rom: ZiskRom, input: Vec) -> Sim { + // Initialize an empty instance + let mut sim = Sim { ctx: SimContext::new(input), operations: ZiskOperations::new(), rom }; + + // Create a new read section for every RO data entry of the rom + for i in 0..sim.rom.ro_data.len() { + sim.ctx.mem.add_read_section(sim.rom.ro_data[i].from, &sim.rom.ro_data[i].data); + } + + // Get registers + //sim.get_regs(); // TODO: ask Jordi + + sim + } + + /// Performs one single step of the simulation + pub fn step(&mut self) { + // Get a mutable reference to the simulation context + let ctx = &mut self.ctx; + + // Get the ZisK instruction corresponding to the current program counter + if !self.rom.insts.contains_key(&ctx.pc) { + panic!("Sim::step() cound not find a rom instruction for pc={}={:x}", ctx.pc, ctx.pc); + } + let inst = &self.rom.insts[&ctx.pc]; + + //println!("Sim::step() executing step={} pc={:x} inst={}", ctx.step, ctx.pc, + // inst.i.to_string()); println!("Sim::step() step={} pc={}", ctx.step, ctx.pc); + + // If this is the last instruction, stop executing + if inst.i.end == 1 { + ctx.end = true; + } + + // Build the value of the a register based on the source specified by the current + // instruction + match inst.i.a_src { + SRC_C => ctx.a = ctx.c, + SRC_MEM => { + let mut addr = inst.i.a_offset_imm0; + if inst.i.a_use_sp_imm1 != 0 { + addr += ctx.sp; + } + ctx.a = ctx.mem.read(addr, 8); + } + SRC_IMM => ctx.a = inst.i.a_offset_imm0 | (inst.i.a_use_sp_imm1 << 32), + SRC_STEP => ctx.a = ctx.step, + SRC_SP => ctx.a = ctx.sp, + _ => panic!("Sim::step() Invalid a_src={} pc={}", inst.i.a_src, ctx.pc), + } + + // Build the value of the b register based on the source specified by the current + // instruction + match inst.i.b_src { + SRC_C => ctx.b = ctx.c, + SRC_MEM => { + let mut addr = inst.i.b_offset_imm0; + if inst.i.b_use_sp_imm1 != 0 { + addr += ctx.sp; + } + ctx.b = ctx.mem.read(addr, 8); + } + SRC_IMM => ctx.b = inst.i.b_offset_imm0 | (inst.i.b_use_sp_imm1 << 32), + SRC_IND => { + let mut addr = (ctx.a as i64 + inst.i.b_offset_imm0 as i64) as u64; + if inst.i.b_use_sp_imm1 != 0 { + addr += ctx.sp; + } + ctx.b = ctx.mem.read(addr, inst.i.ind_width); + } + _ => panic!("Sim::step() Invalid b_src={} pc={}", inst.i.b_src, ctx.pc), + } + + // Check the instruction opcode range + if inst.i.op > 0xFF { + panic!("Sim::step() invalid opcode={}", inst.i.op); + } + + // Get the ZisK operation for this opcode + let operation = self.operations.op_from_code.get(&(inst.i.op as u8)).unwrap(); + + // Call the operation + (ctx.c, ctx.flag) = (operation.f)(ctx.a, ctx.b); + + // Store the value of the c register based on the storage specified by the current + // instruction + match inst.i.store { + STORE_NONE => print!(""), + STORE_MEM => { + let val: i64 = if inst.i.store_ra != 0 { + ctx.pc as i64 + inst.i.jmp_offset2 + } else { + ctx.c as i64 + }; + let mut addr: i64 = inst.i.store_offset; + if inst.i.store_use_sp != 0 { + addr += ctx.sp as i64; + } + ctx.mem.write(addr as u64, val as u64, 8); + //println!{"Sim::step() step={} pc={} writing to memory addr={} val={}", ctx.step, + // ctx.pc, addr, val as u64}; + } + STORE_IND => { + let val: i64 = if inst.i.store_ra != 0 { + ctx.pc as i64 + inst.i.jmp_offset2 + } else { + ctx.c as i64 + }; + let mut addr = inst.i.store_offset; + if inst.i.store_use_sp != 0 { + addr += ctx.sp as i64; + } + addr += ctx.a as i64; + ctx.mem.write(addr as u64, val as u64, inst.i.ind_width); + //println!{"Sim::step() step={} pc={} writing to memory addr={} val={}", ctx.step, + // ctx.pc, addr, val as u64}; + } + _ => panic!("Sim::step() Invalid store={} pc={}", inst.i.store, ctx.pc), + } + + // Set SP, if specified by the current instruction + if inst.i.set_sp != 0 { + ctx.sp = ctx.c; + } else { + ctx.sp += inst.i.inc_sp; + } + + // Set PC, based on current PC, current flag and current instruction + if inst.i.set_pc != 0 { + ctx.pc = (ctx.c as i64 + inst.i.jmp_offset1) as u64; + } else if ctx.flag { + ctx.pc = (ctx.pc as i64 + inst.i.jmp_offset1) as u64; + } else { + ctx.pc = (ctx.pc as i64 + inst.i.jmp_offset2) as u64; + } + + // Increment step counter + ctx.step += 1; + } + + /// Get the output as a vector of u64 + pub fn get_output(&self) -> Vec { + let ctx = &self.ctx; + let n = ctx.mem.read(OUTPUT_ADDR, 8); + let mut addr = OUTPUT_ADDR + 8; + let mut output: Vec = Vec::new(); + for _i in 0..n { + output.push(ctx.mem.read(addr, 8)); + addr += 8; + } + output + } + + /// Get the output as a vector of u32 + pub fn get_output_32(&self) -> Vec { + let ctx = &self.ctx; + let n = ctx.mem.read(OUTPUT_ADDR, 4); + let mut addr = OUTPUT_ADDR + 4; + let mut output: Vec = Vec::new(); + for _i in 0..n { + output.push(ctx.mem.read(addr, 4) as u32); + addr += 4; + } + output + } + + /// Run the whole program + pub fn run(&mut self, opts: SimOptions) { + // While not done + while !self.ctx.end { + //println!("Sim::run() step={} ctx.pc={}", self.ctx.step, self.ctx.pc); // 2147483828 + // Check trace PC + if self.ctx.tracerv_on && (self.ctx.pc % 4 == 0) { + self.ctx.trace_pc = self.ctx.pc; + } + + // Log simulation step, if requested + if (opts.print_step != 0) && ((self.ctx.step % opts.print_step) == 0) { + println!("step={}", self.ctx.step); + } + + // Stop the execution if we exceeded the specified running conditions + if (self.ctx.pc as i64 == opts.to) || (self.ctx.step >= opts.max_steps) { + break; + } + + // Execute the current step + self.step(); + + // Only trace after finishing a riscV instruction + if self.ctx.tracerv_on && ((self.ctx.pc % 4) == 0) { + // Store logs in a vector of strings + let mut changes: Vec = Vec::new(); + + // Get the current state of the registers + let new_regs_array = self.get_regs_array(); + + // For all current registers + for i in 0..new_regs_array.len() { + // If they changed since previous stem, add them to the logs + if new_regs_array[i] != self.ctx.tracerv_current_regs[i] { + changes.push(format!("{}={:x}", REG_NAMES[i], new_regs_array[i])); + self.ctx.tracerv_current_regs[i] = new_regs_array[i]; + } + } + + // Add a log trace including all modified registers + self.ctx.tracerv.push(format!( + "{}: {} -> {}", + self.ctx.tracerv_step, + self.ctx.trace_pc, + changes.join(", ") + )); + + // Increase tracer step counter + self.ctx.tracerv_step += 1; + } + + //println!("Sim::run() done ctx.pc={}", self.ctx.pc); // 2147483828 + } + } + + /// Gets the current values of the 32 registers + pub fn get_regs_array(&self) -> [u64; 32] { + let mut regs_array: [u64; 32] = [0; 32]; + for (i, reg) in regs_array.iter_mut().enumerate() { + *reg = self.ctx.mem.read(SYS_ADDR + (i as u64) * 8, 8); + } + regs_array + } + + /// Gets the current values of the 32 registers, mapped to their corresponding register name + pub fn get_regs(&self) -> HashMap<&str, u64> { + let regs_array = self.get_regs_array(); + let mut reg_values: HashMap<&str, u64> = HashMap::new(); + for i in 0..32 { + reg_values.insert(REG_NAMES[i], regs_array[i]); + } + reg_values + } + + /// Enables the tracer, initializing the current registers to detect differences from now on + pub fn tracerv_on(&mut self) { + self.ctx.tracerv_current_regs = self.get_regs_array(); + self.ctx.tracerv_on = true; + } + + /// Disables the tracer + pub fn tracerv_off(&mut self) { + self.ctx.tracerv_on = false; + } + + /// Gets the log traces + pub fn get_tracerv(&self) -> Vec { + self.ctx.tracerv.clone() + } + + /// Returns if the simulation ended + pub fn terminated(&self) -> bool { + self.ctx.end + } +} diff --git a/simulator/src/sim_context.rs b/simulator/src/sim_context.rs new file mode 100644 index 00000000..7a7e8619 --- /dev/null +++ b/simulator/src/sim_context.rs @@ -0,0 +1,60 @@ +use crate::Mem; +use riscv2zisk::{INPUT_ADDR, MAX_INPUT_SIZE, RAM_ADDR, RAM_SIZE, ROM_ENTRY}; + +/// ZisK simulator context data container, storing the state of the simulation +pub struct SimContext { + pub mem: Mem, + pub a: u64, + pub b: u64, + pub c: u64, + pub flag: bool, + pub sp: u64, + pub pc: u64, + pub step: u64, + pub end: bool, + pub tracerv: Vec, + pub tracerv_on: bool, + pub tracerv_step: u64, + pub tracerv_current_regs: [u64; 32], + pub trace_pc: u64, +} + +/// RisK simulator context implementation +impl SimContext { + /// RisK simulator context constructor + pub fn new(input: Vec) -> SimContext { + let mut ctx = SimContext { + mem: Mem::new(), + a: 0, + b: 0, + c: 0, + flag: false, + sp: 0, + pc: ROM_ENTRY, + step: 0, + end: false, + tracerv: Vec::new(), + tracerv_on: false, + tracerv_step: 0, + tracerv_current_regs: [0; 32], + trace_pc: 0, + }; + + // Check the input data size is inside the proper range + if input.len() > (MAX_INPUT_SIZE - 8) as usize { + panic!("SimContext::new() input size too big size={}", input.len()); + } + + // Create a new empty vector + let buffer: Vec = vec![0; 8]; + + // Add the length and input data read sections + ctx.mem.add_read_section(INPUT_ADDR, &buffer); + ctx.mem.add_read_section(INPUT_ADDR + 8, &input); + + // Add the write section + ctx.mem.add_write_section(RAM_ADDR, RAM_SIZE); + + ctx + } +} diff --git a/simulator/src/sim_options.rs b/simulator/src/sim_options.rs new file mode 100644 index 00000000..f728af08 --- /dev/null +++ b/simulator/src/sim_options.rs @@ -0,0 +1,21 @@ +/// ZisK simulator options structure +pub struct SimOptions { + pub to: i64, + pub max_steps: u64, + pub print_step: u64, +} + +/// Default constructor for SimOptions structure +impl Default for SimOptions { + fn default() -> Self { + Self::new() + } +} + +/// ZisK simulator options structure implementation +impl SimOptions { + /// Zisk simulator options constructor + pub fn new() -> SimOptions { + SimOptions { to: -1, max_steps: 1_000_000, print_step: 0 } + } +}