diff --git a/.gitignore b/.gitignore index ea8c4bf..da74d9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ /target +/.idea +*.iml +*.txt \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..6559645 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,27 @@ +default: + image: rust:latest + +stages: + - build + - test + +build-job: + stage: build + script: + - echo "Compiling the code..." + - cargo build + - echo "Compile complete." + +unit-test-job: + stage: test + script: + - echo "Running unit tests..." + - cargo test + +lint-test-job: + only: + - merge_requests + stage: test + script: + - echo "Linting code..." + - cargo clippy \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c38343f..562ef00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,14 @@ -#![warn(missing_docs)] +mod simulator; -//! # Crate burritos -//! -//! Crate du point d'entrée de burritos -//! -//! ## Liste des crate: -//! -//! - kernel -//! - machine -//! - A remplir +use simulator::machine::Machine; +use simulator::mem_cmp; -/// Cette fonction est le point d'entrée de burritos -/// -/// Elle se contente de parser les arguments de lancement et les transmet ensuite fn main() { - println!("Hello, world!"); + let mut m = Machine::_init_machine(); + let path = "memoryComp.txt".to_string(); + let checker = mem_cmp::MemChecker::from(&path); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&checker, &mut m); + //mem_cmp::Mem_Checker::print_Mem_Checker(&checker); + //Machine::print_memory(&mut m, 0x400000, 0x405000); + Machine::run(&mut m); } diff --git a/src/simulator/decode.rs b/src/simulator/decode.rs new file mode 100644 index 0000000..b388eb4 --- /dev/null +++ b/src/simulator/decode.rs @@ -0,0 +1,95 @@ +use core::num::Wrapping; // Permet d'autoriser les overflow pour les opérations voulues + +#[allow(non_snake_case)] // supprimer le warning snake case (quand les noms de variables ont des majuscules) +#[derive(Debug)] +pub struct Instruction { + pub value : u64, + + pub opcode : u8, + pub rs1 : u8, + pub rs2 : u8, + pub rs3 : u8, + pub rd : u8, + pub funct7 : u8, + pub funct7_smaller : u8, + pub funct3 : u8, + pub shamt : u8, // shamt = imm[5:0] or imm[4:0] (depend of opcode) + + pub imm12_I : u16, + pub imm12_S : u16, + + pub imm12_I_signed : i16, + pub imm12_S_signed : i16, + pub imm13 : i16, + pub imm13_signed : i16, + + pub imm31_12 : u32, + pub imm21_1 : u32, + + pub imm31_12_signed : i32, + pub imm21_1_signed : i32, +} + +#[allow(non_snake_case)] +pub fn decode(val : u64) -> Instruction { + + let value = val; + + let opcode = (val & 0x7f) as u8; + let rs1 = ((val >> 15) & 0x1f) as u8; + let rs2 = ((val >> 20) & 0x1f) as u8; + let rs3 = ((val >> 27) & 0x1f) as u8; + let rd = ((val >> 7) & 0x1f) as u8; + let funct7 = ((val >> 25) & 0x7f) as u8; + let funct7_smaller = funct7 & 0x3e; + + let funct3 = ((val >> 12) & 0x7) as u8; + let imm12_I = ((val >> 20) & 0xfff) as u16; + let imm12_S = (((val >> 20) & 0xfe0) + ((val >> 7) & 0x1f)) as u16; + + let imm12_I_signed = if imm12_I >= 2048 { (Wrapping(imm12_I) - Wrapping(4096)).0 } else { imm12_I } as i16; + let imm12_S_signed = if imm12_S >= 2048 { (Wrapping(imm12_S) - Wrapping(4096)).0 } else { imm12_S } as i16; + + let imm13 = (((val >> 19) & 0x1000) + ((val >> 20) & 0x7e0) + + ((val >> 7) & 0x1e) + ((val << 4) & 0x800)) as i16; + let imm13_signed = if imm13 >= 4096 { imm13 - 8192 } else { imm13 }; + + let imm31_12 = (val & 0xfffff000) as u32; + let imm31_12_signed = imm31_12 as i32; + + let imm21_1 = ((val & 0xff000) + ((val >> 9) & 0x800) + + ((val >> 20) & 0x7fe) + ((val >> 11) & 0x100000)) as u32; + let imm21_1_signed = if imm21_1 >= 1048576 { (Wrapping(imm21_1) - Wrapping(2097152)).0 } else { imm21_1 } as i32; + + let shamt = ((val >> 20) & 0x3f) as u8; + + Instruction { + value, + + opcode, + rs1, + rs2, + rs3, + rd, + funct7, + funct7_smaller, + + funct3, + imm12_I, + imm12_S, + + imm12_I_signed, + imm12_S_signed, + + imm13, + imm13_signed, + + imm31_12, + imm31_12_signed, + + imm21_1, + imm21_1_signed, + + shamt + } +} \ No newline at end of file diff --git a/src/simulator/loader.rs b/src/simulator/loader.rs new file mode 100644 index 0000000..b7b4b36 --- /dev/null +++ b/src/simulator/loader.rs @@ -0,0 +1,34 @@ +use crate::Machine; + +use std::fs; +use std::io; +use std::io::BufRead; + + + + /// Load a file into a new machine + /// + /// `panic!` when size is not 1, 2, 4 or 8 + /// `panic!` when the text does not represents instructions in hexadecimal + /// + /// ### Parameters + /// + /// - **path** the path of the file to load + /// - **size** the number of bytes to write (1, 2, 4 or 8) +pub fn _load(path : &str, instruction_size: i32) -> Machine { + let file = fs::File::open(path).expect("Wrong filename"); + let reader = io::BufReader::new(file); + let mut machine = Machine::_init_machine(); + + for (i,line) in reader.lines().enumerate() { + let res = u64::from_str_radix(&line.unwrap(), 16); + match res { + Ok(value) => { + Machine::write_memory(&mut machine, instruction_size, i*instruction_size as usize, value); + }, + _ => panic!() + } + } + println!("{:x}", Machine::read_memory(& mut machine, 4, 0)); + machine +} \ No newline at end of file diff --git a/src/simulator/machine.rs b/src/simulator/machine.rs new file mode 100644 index 0000000..9aeadef --- /dev/null +++ b/src/simulator/machine.rs @@ -0,0 +1,836 @@ +use std::{ops::{Add, Sub}, io::Write}; + +use crate::simulator::print; + +use super::{decode::{Instruction, decode}}; +use super::global::*; +use std::fs::File; + +/// doit disparaitre +const MEM_SIZE : usize = 0x500000; + +pub trait RegisterNum: Add + Sub + PartialEq + Copy {} + +impl RegisterNum for i64 {} + +impl RegisterNum for f32 {} + + + +pub struct Register { + register: [U; 32] +} + +impl Register { + + pub fn get_reg(&self, position: usize) -> U { + self.register[position] + } + +} + +impl Register { + + pub fn init() -> Register { + Register { + register: [0i64; 32] + } + } + + pub fn set_reg(&mut self, position: usize, value: i64) { + if position != 0 { + self.register[position] = value; + } else { + // Panic ou rien ? (dans le doute pour le moment panic) + // unreachable!("You can't write to zero register") + } + } + +} + +impl Register { + + pub fn init() -> Register { + Register { + register: [0f32; 32] + } + } + + pub fn set_reg(&mut self, position: usize, value: f32) { + self.register[position] = value; + } + +} + +pub struct Machine { + pub pc : u64, + pub sp: usize, + pub int_reg : Register, + pub fp_reg : Register, + pub main_memory : Vec, + pub shiftmask : [u64 ; 64], + pub registers_trace : String // for tests + // futur taille à calculer int memSize = g_cfg->NumPhysPages * g_cfg->PageSize; + //creer une struct cfg(configuration) qui s'initialise avec valeur dans un fichier cfg +} + + +impl Machine { + + pub fn _init_machine() -> Machine { + let mut shiftmask : [u64 ; 64] = [0 ; 64]; + let mut value : u64 = 0xffffffff; + + value = (value << 32) + value; + for item in &mut shiftmask { + *item = value; + value >>= 1; + } + + let mut ret = Machine { + pc : 0, + sp: 0, + int_reg : Register::::init(), + fp_reg : Register::::init(), + main_memory : vec![0; MEM_SIZE], + shiftmask, + registers_trace : String::from("") + }; + + ret.int_reg.set_reg(10, -1); + ret + } + + /// Read from main memory of the machine + /// + /// `panic!` when size is not 1, 2, 4 or 8 + /// + /// ### Parameters + /// + /// - **machine** which contains the main memory + /// - **size** the number of bytes to read (1, 2, 4, 8) + /// - **address** in the memory to read + pub fn read_memory(machine : &mut Machine, size : i32, address : usize) -> u64 { + if ![1, 2, 4, 8].contains(&size) { + panic!("ERROR read_memory : wrong size parameter {size}, must be (1, 2, 4 or 8)"); + } + + let mut ret: u64 = 0; + for i in 0..size { + ret <<= 8; + ret += machine.main_memory[address + i as usize] as u64; + } + ret + } + + /// Write to the main memory of the machine + /// + /// `panic!` when size is not 1, 2, 4 or 8 + /// + /// ### Parameters + /// + /// - **machine** contains the memory + /// - **size** the number of bytes to write (1, 2, 4 or 8) + /// - **address** the address to write to + /// - **value** data to be written + pub fn write_memory(machine: &mut Machine, size: i32, address: usize, value: u64) { + if ![1, 2, 4, 8].contains(&size) { + panic!("ERROR write_memory: WRONG `size` PARAMETER ({size}), must be 1, 2, 4 or 8") + } + for i in 0..size as usize { + let inv_i = size as usize - i - 1; + machine.main_memory[address + i] = ((value & 0xff << (8 * inv_i)) >> (inv_i * 8)) as u8; + } + } + + /// Write the contains of the main memory of the machine + /// in a file called burritos_memory.txt + /// + /// ### Parameters + /// + /// - **machine** contains the memory + pub fn _extract_memory(machine: &mut Machine){ + let file_path = "burritos_memory.txt"; + let write_to_file = |path| -> std::io::Result { + let mut file = File::create(path)?; + file.write_all(&machine.main_memory)?; + Ok(file) + }; + match write_to_file(file_path) { + Err(e) => eprintln!("Failed to write memory to file: {}", e), + Ok(_) => println!("Memory extracted to {}", file_path) + }; + } + + pub fn print_machine_status(machine: &mut Machine) { + println!("######### Machine status #########"); + for i in (0..32).step_by(3) { + print!(">{0: <4} : {1:<16x} ", print::REG_X[i], machine.int_reg.get_reg(i)); + print!(">{0: <4} : {1:<16x} ", print::REG_X[i+1], machine.int_reg.get_reg(i+1)); + if i+2 < 32 { + print!(">{0: <4} : {1:<16x} ", print::REG_X[i+2], machine.int_reg.get_reg(i+2)); + } + println!(); + } + println!("________________SP________________"); + let sp_index = machine.int_reg.get_reg(2); + for i in 0..5 { + println!("SP+{:<2} : {:16x}", i*8, Self::read_memory(machine, 8, (sp_index + i*8) as usize)); + } + println!("##################################"); + } + + pub fn string_registers(machine: &mut Machine) -> String { + let mut s = String::from(""); + for i in 0..32 { + s.push_str(format!("{} ", machine.int_reg.get_reg(i)).as_str()); + } + s + } + + /// Execute the instructions table of a machine putted in param + /// + /// ### Parameters + /// + /// - **machine** which contains a table of instructions + pub fn run(machine : &mut Machine){ + while Machine::one_instruction(machine) == 0 {} + println!("trace : \n{}", machine.registers_trace); + } + + /// execute the current instruction + /// + /// ### Parameters + /// + /// - **machine** which contains a table of instructions and a pc to the actual instruction + pub fn one_instruction(machine :&mut Machine) -> i32 { + + let unsigned_reg1 : u64; + let unsigned_reg2 : u64; + let long_result : i128; + + /*__int128 longResult; + int32_t local_data_a, local_data_b; + int64_t localLongResult; + uint32_t local_data_aUnsigned, local_data_bUnsigned; + int32_t localResult; + float localFloat; + uint64_t value;*/ + + if machine.main_memory.len() <= machine.pc as usize { + panic!("ERROR : number max of instructions rushed"); + } + let mut val: [u8; 4] = [0; 4]; + for (i, mut _item) in val.iter_mut().enumerate() { + _item = &mut machine.main_memory[machine.pc as usize + i]; + } + + let val = u32::from_be_bytes(val) as u64; + let inst : Instruction = decode(val); + Self::print_machine_status(machine); + println!("executing instruction : {:016x} at pc {:x}", val, machine.pc); + println!("{}", print::print(decode(val), machine.pc as i32)); + let trace = Self::string_registers(machine); + machine.registers_trace.push_str(format!("{}\n", trace).as_str()); + + machine.pc += 4; + + match inst.opcode { + RISCV_LUI => { + machine.int_reg.set_reg(inst.rd as usize, inst.imm31_12 as i64); + }, + RISCV_AUIPC => { + machine.int_reg.set_reg(inst.rd as usize,machine.pc as i64 - 4 + inst.imm31_12 as i64); + }, + RISCV_JAL => { + machine.int_reg.set_reg(inst.rd as usize, machine.pc as i64); + machine.pc = (machine.pc as i64 + inst.imm21_1_signed as i64 - 4) as u64; + }, + RISCV_JALR => { + let tmp = machine.pc; + machine.pc = (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64) as u64 & 0xfffffffe; + machine.int_reg.set_reg(inst.rd as usize, tmp as i64); + }, + + //****************************************************************************************** + // Treatment for: BRANCH INSTRUCTIONS + RISCV_BR => { + match inst.funct3 { + RISCV_BR_BEQ => { + if machine.int_reg.get_reg(inst.rs1 as usize) == machine.int_reg.get_reg(inst.rs2 as usize) { + machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64; + } + }, + RISCV_BR_BNE => { + if machine.int_reg.get_reg(inst.rs1 as usize) != machine.int_reg.get_reg(inst.rs2 as usize) { + machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64; + } + }, + RISCV_BR_BLT => { + if machine.int_reg.get_reg(inst.rs1 as usize) < machine.int_reg.get_reg(inst.rs2 as usize) { + machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64; + } + }, + RISCV_BR_BGE => { + if machine.int_reg.get_reg(inst.rs1 as usize) >= machine.int_reg.get_reg(inst.rs2 as usize) { + machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64; + } + }, + RISCV_BR_BLTU => { + if machine.int_reg.get_reg(inst.rs1 as usize) < machine.int_reg.get_reg(inst.rs2 as usize) { + machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64; + } + }, + RISCV_BR_BGEU => { + if machine.int_reg.get_reg(inst.rs1 as usize) >= machine.int_reg.get_reg(inst.rs2 as usize) { + machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64; + } + }, + _ => { + panic!("In BR switch case, this should never happen... Instr was {}", inst.value); + } + } + }, + + //****************************************************************************************** + // Treatment for: LOAD INSTRUCTIONS + RISCV_LD => { + match inst.funct3 { + RISCV_LD_LB | RISCV_LD_LBU => { + let tmp = Self::read_memory(machine, 1, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64) as usize) as i64; + machine.int_reg.set_reg(inst.rd as usize, tmp); + }, + RISCV_LD_LH | RISCV_LD_LHU => { + let tmp = Self::read_memory(machine, 2, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64) as usize) as i64; + machine.int_reg.set_reg(inst.rd as usize, tmp); + }, + RISCV_LD_LW | RISCV_LD_LWU => { + let tmp = Self::read_memory(machine, 4, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64) as usize) as i64; + machine.int_reg.set_reg(inst.rd as usize, tmp); + }, + RISCV_LD_LD => { + let tmp = Self::read_memory(machine, 8, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64) as usize) as i64; + machine.int_reg.set_reg(inst.rd as usize, tmp); + }, + _ => { + panic!("In LD switch case, this should never happen... Instr was {}", inst.value); + } + } + }, + // store instructions + RISCV_ST => { + match inst.funct3 { + RISCV_ST_STB => { + Self::write_memory(machine, 1, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_S_signed as i64) as usize, machine.int_reg.get_reg(inst.rs2 as usize) as u64); + }, + RISCV_ST_STH => { + Self::write_memory(machine, 2, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_S_signed as i64) as usize, machine.int_reg.get_reg(inst.rs2 as usize) as u64); + }, + RISCV_ST_STW => { + Self::write_memory(machine, 4, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_S_signed as i64) as usize, machine.int_reg.get_reg(inst.rs2 as usize) as u64); + }, + RISCV_ST_STD => { + Self::write_memory(machine, 8, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_S_signed as i64) as usize, machine.int_reg.get_reg(inst.rs2 as usize) as u64); + }, + _ => { + panic!("In ST switch case, this should never happen... Instr was {}", inst.value); + } + } + } + //****************************************************************************************** + // Treatment for: OPI INSTRUCTIONS + RISCV_OPI => { + match inst.funct3 { + RISCV_OPI_ADDI => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64); + }, + RISCV_OPI_SLTI => { + machine.int_reg.set_reg(inst.rd as usize, (machine.int_reg.get_reg(inst.rs1 as usize) < inst.imm12_I_signed as i64) as i64); + }, + RISCV_OPI_XORI => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) ^ inst.imm12_I_signed as i64); + }, + RISCV_OPI_ORI => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) | inst.imm12_I_signed as i64); + }, + RISCV_OPI_ANDI => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) & inst.imm12_I_signed as i64); + }, + RISCV_OPI_SLLI => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) << inst.shamt); + }, + RISCV_OPI_SRI => { + if inst.funct7_smaller == RISCV_OPI_SRI_SRLI { + machine.int_reg.set_reg(inst.rd as usize, (machine.int_reg.get_reg(inst.rs1 as usize) >> inst.shamt) & machine.shiftmask[inst.shamt as usize] as i64); + } else { // SRAI + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) >> inst.shamt); + } + } + _ => { panic!("In OPI switch case, this should never happen... Instr was %x\n {}", inst.value); } + } + }, + + RISCV_OP => { + if inst.funct7 == 1 { + match inst.funct3 { + RISCV_OP_M_MUL => { + long_result = (machine.int_reg.get_reg(inst.rs1 as usize) * machine.int_reg.get_reg(inst.rs2 as usize)) as i128; + machine.int_reg.set_reg(inst.rd as usize, (long_result & 0xffffffffffffffff) as i64); + }, + RISCV_OP_M_MULH => { + long_result = (machine.int_reg.get_reg(inst.rs1 as usize) * machine.int_reg.get_reg(inst.rs2 as usize)) as i128; + machine.int_reg.set_reg(inst.rd as usize, ((long_result >> 64) & 0xffffffffffffffff) as i64); + }, + RISCV_OP_M_MULHSU => { + unsigned_reg2 = machine.int_reg.get_reg(inst.rs2 as usize) as u64; + long_result = (machine.int_reg.get_reg(inst.rs1 as usize) as u64 * unsigned_reg2) as i128; + machine.int_reg.set_reg(inst.rd as usize, ((long_result >> 64) & 0xffffffffffffffff) as i64); + }, + // VOIR CE QUE FAIT EXACTEMENT CE TRUC , PK on converve + /* + * VOIR SI LES CAST machine.int_reg[....] = i128*u64 as u32 FAUSSE RESULTAT (suit pas la logique du code c++) + * WHAT DA HECK + */ + RISCV_OP_M_MULHU => { + unsigned_reg1 = machine.int_reg.get_reg(inst.rs1 as usize) as u64; + unsigned_reg2 = machine.int_reg.get_reg(inst.rs2 as usize) as u64; + long_result = (unsigned_reg1 * unsigned_reg2) as i128; + machine.int_reg.set_reg(inst.rd as usize, ((long_result >> 64) & 0xffffffffffffffff) as i64); + }, + RISCV_OP_M_DIV => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) / machine.int_reg.get_reg(inst.rs2 as usize)); + } + _ => { + panic!("RISCV_OP : funct7 = 1 (Multiplication) :: Error\n"); + } + } + } else { + match inst.funct3 { + RISCV_OP_ADD => { + if inst.funct7 == RISCV_OP_ADD_ADD { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) + machine.int_reg.get_reg(inst.rs2 as usize)); + } else { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) - machine.int_reg.get_reg(inst.rs2 as usize)); + } + }, + RISCV_OP_SLL => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) << (machine.int_reg.get_reg(inst.rs2 as usize) & 0x3f)); + }, + RISCV_OP_SLT => { + if machine.int_reg.get_reg(inst.rs1 as usize) < machine.int_reg.get_reg(inst.rs2 as usize) { + machine.int_reg.set_reg(inst.rd as usize, 1); + } else { + machine.int_reg.set_reg(inst.rd as usize, 0); + } + }, + RISCV_OP_SLTU => { + unsigned_reg1 = machine.int_reg.get_reg(inst.rs1 as usize) as u64; + unsigned_reg2 = machine.int_reg.get_reg(inst.rs2 as usize) as u64; + if unsigned_reg1 < unsigned_reg2 { + machine.int_reg.set_reg(inst.rd as usize, 1); + } else { + machine.int_reg.set_reg(inst.rd as usize, 0); + } + }, + RISCV_OP_XOR => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) ^ machine.int_reg.get_reg(inst.rs2 as usize)); + }, + RISCV_OP_SR => { + // RISCV_OP_SR_SRL inaccessible + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) >> machine.int_reg.get_reg(inst.rs2 as usize)); + }, + RISCV_OP_OR => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) | machine.int_reg.get_reg(inst.rs2 as usize)); + }, + RISCV_OP_AND => { + machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) & machine.int_reg.get_reg(inst.rs2 as usize)); + }, + _ => { + panic!("RISCV_OP undefined case\n"); + } + }//LA + } + }, + //****************************************************************************************** + // Treatment for OPIW INSTRUCTIONS + RISCV_OPIW => { + let local_data = machine.int_reg.get_reg(inst.rs1 as usize); + match inst.funct3 { + RISCV_OPIW_ADDIW => { + let result = local_data + inst.imm12_I_signed as i64; + machine.int_reg.set_reg(inst.rd as usize, result); + }, + RISCV_OPIW_SLLIW => { + let result = local_data << inst.shamt; + machine.int_reg.set_reg(inst.rd as usize, result); + }, + RISCV_OPIW_SRW => { + let result = if inst.funct7 == RISCV_OPIW_SRW_SRLIW { + (local_data >> inst.shamt) & machine.shiftmask[32 + inst.shamt as usize] as i64 + } else { // SRAIW + local_data >> inst.shamt + }; + machine.int_reg.set_reg(inst.rd as usize, result); + }, + _ => { + panic!("In OPI switch case, this should never happen... Instr was {}\n", inst.value); + } + } + }, + //****************************************************************************************** + // Treatment for: OPW INSTRUCTIONS + RISCV_OPW => { + if inst.funct7 == 1 { // rv64m + let local_data_a = machine.int_reg.get_reg(inst.rs1 as usize) & 0xffffffff; + let local_data_b = machine.int_reg.get_reg(inst.rs2 as usize) & 0xffffffff; + let local_data_a_unsigned = machine.int_reg.get_reg(inst.rs1 as usize) & 0xffffffff; + let local_data_b_unsigned = machine.int_reg.get_reg(inst.rs2 as usize) & 0xffffffff; + + // Match case for multiplication operations (in standard extension RV32M) + match inst.funct3 { + RISCV_OPW_M_MULW => { + machine.int_reg.set_reg(inst.rd as usize, local_data_a * local_data_b); + }, + RISCV_OPW_M_DIVW => { + machine.int_reg.set_reg(inst.rd as usize, local_data_a / local_data_b); + }, + RISCV_OPW_M_DIVUW => { + machine.int_reg.set_reg(inst.rd as usize, local_data_a_unsigned / local_data_b_unsigned); + }, + RISCV_OPW_M_REMW => { + machine.int_reg.set_reg(inst.rd as usize, local_data_a % local_data_b); + }, + RISCV_OPW_M_REMUW => { + machine.int_reg.set_reg(inst.rd as usize, local_data_a_unsigned % local_data_b_unsigned); + }, + _ => { + panic!("this instruction ({}) doesn't exists", inst.value); + } + } + } else { // others rv64 OPW operations + let local_dataa = machine.int_reg.get_reg(inst.rs1 as usize) & 0xffffffff; + let local_datab = machine.int_reg.get_reg(inst.rs2 as usize) & 0xffffffff; + + // Match case for base OP operation + match inst.funct3 { + RISCV_OPW_ADDSUBW => { + if inst.funct7 == RISCV_OPW_ADDSUBW_ADDW { + machine.int_reg.set_reg(inst.rd as usize, local_dataa + local_datab); + } else { // SUBW + machine.int_reg.set_reg(inst.rd as usize, local_dataa - local_datab); + } + }, + RISCV_OPW_SLLW => { + machine.int_reg.set_reg(inst.rd as usize, local_dataa << (local_datab & 0x1f)); + }, + RISCV_OPW_SRW => { + if inst.funct7 == RISCV_OPW_SRW_SRLW { + machine.int_reg.set_reg(inst.rd as usize, local_dataa >> (local_datab & 0x1f) & machine.shiftmask[32 + local_datab as usize] as i64); + } else { // SRAW + machine.int_reg.set_reg(inst.rd as usize, local_dataa >> (local_datab & 0x1f)); + } + }, + _ => { + panic!("this instruction ({}) doesn't exists", inst.value); + } + } + } + }, + //****************************************************************************************** + // Treatment for: Simple floating point extension + RISCV_FP => { + match inst.funct7 { + RISCV_FP_ADD => { + machine.fp_reg.set_reg(inst.rd as usize, machine.fp_reg.get_reg(inst.rs1 as usize) + machine.fp_reg.get_reg(inst.rs2 as usize)); + }, + RISCV_FP_SUB => { + machine.fp_reg.set_reg(inst.rd as usize, machine.fp_reg.get_reg(inst.rs1 as usize) - machine.fp_reg.get_reg(inst.rs2 as usize)); + }, + RISCV_FP_MUL => { + machine.fp_reg.set_reg(inst.rd as usize, machine.fp_reg.get_reg(inst.rs1 as usize) * machine.fp_reg.get_reg(inst.rs2 as usize)); + }, + RISCV_FP_DIV => { + machine.fp_reg.set_reg(inst.rd as usize, machine.fp_reg.get_reg(inst.rs1 as usize) / machine.fp_reg.get_reg(inst.rs2 as usize)); + }, + RISCV_FP_SQRT => { + machine.fp_reg.set_reg(inst.rd as usize, machine.fp_reg.get_reg(inst.rs1 as usize).sqrt()); + }, + RISCV_FP_FSGN => { + let local_float = machine.fp_reg.get_reg(inst.rs1 as usize); + match inst.funct3 { + RISCV_FP_FSGN_J => { + if machine.fp_reg.get_reg(inst.rs2 as usize) < 0f32 { + machine.fp_reg.set_reg(inst.rd as usize, -local_float); + } else { + machine.fp_reg.set_reg(inst.rd as usize, local_float); + } + } + RISCV_FP_FSGN_JN => { + if machine.fp_reg.get_reg(inst.rs2 as usize) < 0f32 { + machine.fp_reg.set_reg(inst.rd as usize, local_float); + } else { + machine.fp_reg.set_reg(inst.rd as usize, -local_float); + } + } + RISCV_FP_FSGN_JX => { + if (machine.fp_reg.get_reg(inst.rs2 as usize) < 0.0 && machine.fp_reg.get_reg(inst.rs1 as usize) >= 0.0) || (machine.fp_reg.get_reg(inst.rs2 as usize) >= 0.0 && machine.fp_reg.get_reg(inst.rs1 as usize) < 0.0) { + machine.fp_reg.set_reg(inst.rd as usize, -local_float); + } else { + machine.fp_reg.set_reg(inst.rd as usize, local_float); + } + } + _ => { + panic!("this instruction ({}) doesn't exists", inst.value); + } + } + }, + RISCV_FP_MINMAX => { + let r1 = machine.fp_reg.get_reg(inst.rs1 as usize); + let r2 = machine.fp_reg.get_reg(inst.rs2 as usize); + match inst.funct3 { + RISCV_FP_MINMAX_MIN => { + machine.fp_reg.set_reg(inst.rd as usize, if r1 < r2 {r1} else {r2}); + }, + RISCV_FP_MINMAX_MAX => { + machine.fp_reg.set_reg(inst.rd as usize, if r1 > r2 {r1} else {r2}); + }, + _ => { + panic!("this instruction ({}) doesn't exists", inst.value); + } + } + }, + RISCV_FP_FCVTW => { + if inst.rs2 == RISCV_FP_FCVTW_W { + machine.int_reg.set_reg(inst.rd as usize, machine.fp_reg.get_reg(inst.rs1 as usize) as i64); + } else { + machine.int_reg.set_reg(inst.rd as usize, (machine.fp_reg.get_reg(inst.rs1 as usize) as u64) as i64); + } + }, + RISCV_FP_FCVTS => { + if inst.rs2 == RISCV_FP_FCVTS_W { + machine.fp_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) as f32); + } else { + machine.fp_reg.set_reg(inst.rd as usize, (machine.int_reg.get_reg(inst.rs1 as usize) as u32) as f32); + } + }, + RISCV_FP_FMVW => { + machine.fp_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) as f32); + }, + RISCV_FP_FMVXFCLASS => { + if inst.funct3 == RISCV_FP_FMVXFCLASS_FMVX { + machine.int_reg.set_reg(inst.rd as usize, machine.fp_reg.get_reg(inst.rs1 as usize) as i64); + } else { + panic!("Fclass instruction is not handled in riscv simulator"); + } + }, + RISCV_FP_FCMP => { + match inst.funct3 { + RISCV_FP_FCMP_FEQ => { + machine.int_reg.set_reg(inst.rd as usize, (machine.fp_reg.get_reg(inst.rs1 as usize) == machine.fp_reg.get_reg(inst.rs2 as usize)) as i64); + }, + RISCV_FP_FCMP_FLT => { + machine.int_reg.set_reg(inst.rd as usize, (machine.fp_reg.get_reg(inst.rs1 as usize) < machine.fp_reg.get_reg(inst.rs2 as usize)) as i64); + }, + RISCV_FP_FCMP_FLE => { + machine.int_reg.set_reg(inst.rd as usize, (machine.fp_reg.get_reg(inst.rs1 as usize) <= machine.fp_reg.get_reg(inst.rs2 as usize)) as i64); + }, + _ => { + panic!("this instruction ({}) doesn't exists", inst.value); + } + } + }, + _ => { + panic!("this instruction ({}) doesn't exists", inst.value); + } + } + } + RISCV_SYSTEM => { + // temporary return value to stop the loop of run + // before we can use system call + return 1; + } + _ => { panic!("{:x} opcode non géré pc : {:x}", inst.opcode, machine.pc)}, + } + + 0 + + } + + /// print memory FOR DEBUG + /// + /// "@"adresse [16 bytes] + pub fn _print_memory(machine : &mut Machine, from: usize, to: usize) { + for i in from..to { + if i%16 == 0 { + print!("\n@{:04x} ", i); + } + print!("{:02x}", machine.main_memory[i]); + } + println!(); + } + +} + +#[cfg(test)] +mod test { + use std::fs; + + use crate::simulator::{machine::Machine, mem_cmp}; + + #[test] + fn test_read_memory() { + let mut m = Machine::_init_machine(); + m.main_memory[4] = 43; + m.main_memory[5] = 150; + assert_eq!((43 << 8) + 150, Machine::read_memory(&mut m, 2, 4)); + } + + #[test] + fn test_write_memory() { + let mut m = Machine::_init_machine(); + Machine::write_memory(&mut m, 2, 6, (43 << 8) + 150); + assert_eq!(43, m.main_memory[6]); + assert_eq!(150, m.main_memory[7]); + Machine::write_memory(&mut m, 4, 8, (52 << 24) + (20 << 16) + (43 << 8) + 150); + assert_eq!(52, m.main_memory[8]); + assert_eq!(20, m.main_memory[9]); + assert_eq!(43, m.main_memory[10]); + assert_eq!(150, m.main_memory[11]); + } + + #[test] + fn test_comp() { + let mut m = Machine::_init_machine(); + let path_before = "memoryComp.txt".to_string(); + let path_after = "memoryCompEnd.txt".to_string(); + let memory_before = mem_cmp::MemChecker::from(&path_before); + let memory_after = mem_cmp::MemChecker::from(&path_after); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m); + Machine::run(&mut m); + + let path_trace = "memoryCompTrace.txt".to_string(); + let expected_trace = fs::read_to_string(path_trace).unwrap(); + + assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m)); + assert!(expected_trace.contains(m.registers_trace.as_str())); + } + + #[test] + fn test_div() { + let mut m = Machine::_init_machine(); + let path_before = "memoryDiv.txt".to_string(); + let path_after = "memoryDivEnd.txt".to_string(); + let memory_before = mem_cmp::MemChecker::from(&path_before); + let memory_after = mem_cmp::MemChecker::from(&path_after); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m); + Machine::run(&mut m); + + let path_trace = "memoryDivTrace.txt".to_string(); + let expected_trace = fs::read_to_string(path_trace).unwrap(); + + assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m)); + assert!(expected_trace.contains(m.registers_trace.as_str())); + } + + #[test] + fn test_if() { + let mut m = Machine::_init_machine(); + let path_before = "memoryIf.txt".to_string(); + let path_after = "memoryIfEnd.txt".to_string(); + let memory_before = mem_cmp::MemChecker::from(&path_before); + let memory_after = mem_cmp::MemChecker::from(&path_after); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m); + Machine::run(&mut m); + + let path_trace = "memoryIfTrace.txt".to_string(); + let expected_trace = fs::read_to_string(path_trace).unwrap(); + + assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m)); + assert!(expected_trace.contains(m.registers_trace.as_str())); + } + + #[test] + fn test_jump() { + let mut m = Machine::_init_machine(); + let path_before = "memoryJump.txt".to_string(); + let path_after = "memoryJumpEnd.txt".to_string(); + let memory_before = mem_cmp::MemChecker::from(&path_before); + let memory_after = mem_cmp::MemChecker::from(&path_after); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m); + Machine::run(&mut m); + + let path_trace = "memoryJumpTrace.txt".to_string(); + let expected_trace = fs::read_to_string(path_trace).unwrap(); + + assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m)); + assert!(expected_trace.contains(m.registers_trace.as_str())); + } + + #[test] + fn test_mul() { + let mut m = Machine::_init_machine(); + let path_before = "memoryMul.txt".to_string(); + let path_after = "memoryMulEnd.txt".to_string(); + let memory_before = mem_cmp::MemChecker::from(&path_before); + let memory_after = mem_cmp::MemChecker::from(&path_after); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m); + Machine::run(&mut m); + + let path_trace = "memoryMulTrace.txt".to_string(); + let expected_trace = fs::read_to_string(path_trace).unwrap(); + + assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m)); + assert!(expected_trace.contains(m.registers_trace.as_str())); + } + + #[test] + fn test_ret() { + let mut m = Machine::_init_machine(); + let path_before = "memoryRet.txt".to_string(); + let path_after = "memoryRetEnd.txt".to_string(); + let memory_before = mem_cmp::MemChecker::from(&path_before); + let memory_after = mem_cmp::MemChecker::from(&path_after); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m); + Machine::run(&mut m); + + let path_trace = "memoryRetTrace.txt".to_string(); + let expected_trace = fs::read_to_string(path_trace).unwrap(); + + assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m)); + assert!(expected_trace.contains(m.registers_trace.as_str())); + } + + #[test] + fn test_sub() { + let mut m = Machine::_init_machine(); + let path_before = "memorySub.txt".to_string(); + let path_after = "memorySubEnd.txt".to_string(); + let memory_before = mem_cmp::MemChecker::from(&path_before); + let memory_after = mem_cmp::MemChecker::from(&path_after); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m); + Machine::run(&mut m); + + let path_trace = "memorySubTrace.txt".to_string(); + let expected_trace = fs::read_to_string(path_trace).unwrap(); + + assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m)); + assert!(expected_trace.contains(m.registers_trace.as_str())); + } + + #[test] + fn test_switch() { + let mut m = Machine::_init_machine(); + let path_before = "memorySwitch.txt".to_string(); + let path_after = "memorySwitchEnd.txt".to_string(); + let memory_before = mem_cmp::MemChecker::from(&path_before); + let memory_after = mem_cmp::MemChecker::from(&path_after); + mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m); + Machine::run(&mut m); + + let path_trace = "memorySwitchTrace.txt".to_string(); + let expected_trace = fs::read_to_string(path_trace).unwrap(); + + assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m)); + assert!(expected_trace.contains(m.registers_trace.as_str())); + } +} diff --git a/src/simulator/mem_cmp.rs b/src/simulator/mem_cmp.rs new file mode 100644 index 0000000..780437e --- /dev/null +++ b/src/simulator/mem_cmp.rs @@ -0,0 +1,404 @@ +use std::fs; +use std::io::BufRead; +use std::io::BufReader; +use std::io::Lines; +use crate::Machine; + +const MEM_SIZE : usize = 4096; + + +/* TRUCS MANQUANTS +* Verifier qu'il y a un nombre pair de caractere hexa dans la ligne correspondante d'une section du fichier source +* Sinon on ne peut pas automatiquement remplir chaque octect car 2 hexa = 1 octet +*/ + +/* FORMAT FICHIER.TXT Représentant la mémoire apres éxecution d'un prog +* PC +* SP +* Section_1 +* Section_2 +* ... +* ... +* Section_n +*/ + +/* Chaque section se divise en 3 parties, sur 2 lignes de texte +* addr ESPACE len +* content +*/ + +//content est une suite hexadécimale + +//Section dans le fichier, champ String car informations proviennent d'un fichier txt +pub struct SectionFormat{ + addr: String, + len: String, + content: String, +} + +//Section dans le programme +pub struct Section{ + addr: usize, // adresse dans la mémoire + len: usize, // nombre d'octets de la donnée à addr + content: Vec, // la donnée en question +} + +/* +* Voir si instanciation d'une structure deplace les valeurs "locales" à la méthode from, je sais plus .... + */ +impl Section{ + + fn from(section: &SectionFormat) -> Section { + + let mut content: Vec = Vec::new(); + let addr: usize = string_hex_to_usize(§ion.addr); + let len: usize = string_hex_to_usize(§ion.len); + + let mut tmp_a: char = ' '; + + for (i, c) in section.content.chars().enumerate(){ + + if i%2 == 0 { + tmp_a = c; + } + else { + content.push(two_hex_to_u8(tmp_a,c)); + } + } + + Section{addr, len, content} + } + + + fn print_section(s: &Section){ + println!("ADDR :: {:x}", s.addr); + println!("LEN :: {:x}", s.len); + println!("CONTENT :: {:?}", s.content); + } +} + +/* +* Representation de l'etat de la mémoire (apres execution.... a confirmer), sous forme de sections +*/ +pub struct MemChecker{ + pc: usize, + sp: usize, + sections: Vec
, +} + + +impl MemChecker{ + + ///Translate lines of a file in e Vector of String + ///We need this method to parse the memory we received + /// + /// ### Parameters + /// + /// - **Lines** The file to parse + /// + /// ### Return + /// - A vector of String where each line of the file os an element of the vector + fn vect_from_lines(lines: &mut Lines>, pc: &mut usize, sp: &mut usize) -> Vec{ + let mut vector = Vec::new(); + for (_,line) in lines.enumerate() { + vector.push(line.unwrap()); + } + let size = vector.len(); + *pc = string_hex_to_usize(vector.get(size - 2).expect("0")); + *sp = string_hex_to_usize(vector.get(size - 1).expect("0")); + vector + } + + /// Fill a mem checker from a file (here the mock memory) + /// Extract the values of pc, sp and sections + /// + /// ### Parameter + /// -**path** addr to the file + /// + /// ### Return + /// Mem-checker filled + pub fn from(path: &String) -> MemChecker { + + let file = fs::File::open(path).expect("Wrong filename"); + + let reader = BufReader::new(file); + let mut lines = reader.lines(); + + let mut pc: usize = 0; + let mut sp: usize = 0; + let vector = MemChecker::vect_from_lines(&mut lines, &mut pc, &mut sp); + + let mut sections: Vec
= Vec::new(); + let mut tmp_addr_str: String = String::new(); + let mut tmp_len_str: String = String::new(); + + let default = String::new(); + for i in 0..vector.len()-2 { + let current_line = vector.get(i).unwrap_or(&default); + + //Lecture des sections + if i % 2 == 0 { + //lecture ligne ADDR LEN + let next_word_index = current_line.find(' ').unwrap(); + tmp_addr_str = String::from(¤t_line[0..next_word_index]); + tmp_len_str = String::from(¤t_line[next_word_index+1..]); + } + else { + //lecture ligne CONTENT + let section_f = SectionFormat{ + addr: tmp_addr_str.clone(), + len: tmp_len_str.clone(), + content: current_line.clone().replace(' ', ""), + }; + sections.push(Section::from(§ion_f)); + } + + } + + MemChecker{pc, sp, sections} + } + + + /// Print the content of a Mem_Checker + /// + /// ### Parameter + /// + /// - **m_c** Contains the data we want to print + pub fn print_mem_checker(m_c: &MemChecker){ + println!("PC :: {:x}", m_c.pc); + println!("SP :: {:x}", m_c.sp); + + for(i,s) in m_c.sections.iter().enumerate() { + println!("\nSection {}\n", i); + Section::print_section(s); + } + + } + + /// Fill a machine's memory from a Mem Chacker + /// + /// ### Parameters + /// + /// - **m_c** contains the data + /// - **machine** contains the memry to fill + pub fn fill_memory_from_mem_checker(m_c: &MemChecker, machine: &mut Machine){ + + machine.sp = m_c.sp; + machine.int_reg.set_reg(2, m_c.sp as i64); + machine.pc = m_c.pc as u64; + + + for section in m_c.sections.iter() { + + for (i,b) in section.content.iter().enumerate() { + machine.main_memory[section.addr + i] = *b; + } + } + } + + + /* + * FOR DEBUG + */ + fn compare_print_m_c_machine(m_c: &MemChecker, machine: &mut Machine){ + + MemChecker::print_mem_checker(m_c); + + for section in m_c.sections.iter() { + + print!("\n\n"); + + println!("Content addr : {}", section.addr); + println!("Content len (number of bytes) : {}", section.len); + + for i in 0..section.len { + println!("mem[{}] = {}", section.addr + i, machine.main_memory[section.addr + i]); + } + } + + } + + /// Compare sections of a memChecker and a machine memory + /// + /// ### Parameters + /// + /// - **m_c** contains section of the memory checker + /// - **machine** contains the main memory + pub fn compare_machine_memory(m_c: &MemChecker, machine: &Machine) -> bool { + m_c.sections.iter().map(|section| { + (0..section.len).into_iter().all(|i| machine.main_memory[section.addr + i] == section.content[i]) + }).all(|e| e) + } + +} + + + +fn string_hex_to_usize(s: &String) -> usize { + + if s.is_empty() { + return 0; + } + + let max_pow = (s.len()-1) as u32; + let mut ret_value: usize = 0; + let base: usize = 16; + + for (i,c )in s.chars().enumerate(){ + //println!("Current char :: {} :: Current pow :: {} ::", c, max_pow - (i as u32)); + let tmp: usize = one_hex_to_dec(c) as usize; + ret_value += base.pow(max_pow - (i as u32))*tmp; + } + + ret_value +} + + + +/* +* c doit etre un caractère hexadécimale + */ +fn one_hex_to_dec(c: char) -> u8 { + + match c { + 'A' | 'a' => 10, + 'B' | 'b' => 11, + 'C' | 'c' => 12, + 'D' | 'd' => 13, + 'E' | 'e' => 14, + 'F' | 'f' => 15, + _ => { + c.to_digit(10).unwrap() as u8 + }, + } +} + + + +fn two_hex_to_u8(c1: char, c2: char) -> u8 { + let a = one_hex_to_dec(c1); + let b = one_hex_to_dec(c2); + + 16*a + b +} + + + +/* +* Juste pour voir si via BufReader les \n sont présent, apres test il s'avère que non +* De toute facon on limitera d'une section la lecture par len + */ +fn test_show_sections_file(){ + let file = fs::File::open("test_file_section.txt").expect("Wrong filename"); + let reader = BufReader::new(file); + + for line in reader.lines() { + //println!("Tailles de la ligne : {}", + let current = line.unwrap(); + //println!("Taille de la ligne : {}", current.len()); // En effet pas de \n dans chaque line, parfait + println!("{}", ¤t); + } + +} + + + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fill_memory(){ + let path = "osef".to_string(); + let m_c = MemChecker::from(&path); + let mut machine = Machine::_init_machine(); + + MemChecker::fill_memory_from_mem_checker(&m_c, &mut machine); + + print!("\n Comparing memory from loaded context\n\n"); + + MemChecker::compare_print_m_c_machine(&m_c, &mut machine); + + } + + #[test] + fn test_enum_start_at_zero(){ + let v = vec![1,2,3]; + + for (i,val) in v.iter().enumerate() { + println!("i = {} :: v[i] = {}", i, val); + } + } + + #[test] + fn test_create_mem_checker(){ + let path: String = "osef".to_string(); + let m_c = MemChecker::from(&path); + MemChecker::print_mem_checker(&m_c); + } + + + #[test] + fn test_string_hex_to_usize(){ + let s = String::from("AE1F20"); + //println!("taille de string : {}", s.len()); + let expected: usize = 11411232; + let result = string_hex_to_usize(&s); + + assert_eq!(expected,result); + } + + #[test] + fn tmp_fct_read_file(){ + println!("Reading A file \n"); + test_show_sections_file(); + } + + #[test] + fn test_create_section_content(){ + let section_format = SectionFormat{ + addr: "0".to_string(), + len: "0".to_string(), + content: "00FF0AA0A5".to_string(), + }; + + let section = Section::from(§ion_format); + let expected_vec: Vec = vec![0u8, 255u8, 10u8, 160u8, 165u8]; + + //println!("Vec from created section {:?}", §ion.content); + //println!("Expected vec {:?}", &expected_vec); + + assert_eq!(section.content, expected_vec); + } + + #[test] + fn test_hex_1(){ + let b = two_hex_to_u8('0', '0'); + assert_eq!(0u8, b); + } + + #[test] + fn test_hex_2(){ + let b = two_hex_to_u8('F', 'F'); + assert_eq!(255u8, b); + } + + #[test] + fn test_hex_3(){ + let b = two_hex_to_u8('0', 'A'); + assert_eq!(10u8, b); + } + + #[test] + fn test_hex_4(){ + let b = two_hex_to_u8('A', '0'); + assert_eq!(160u8, b); + } + + #[test] + fn test_hex_5(){ + let b = two_hex_to_u8('A', '5'); + assert_eq!(165u8, b); + } +} diff --git a/src/simulator/mod.rs b/src/simulator/mod.rs new file mode 100644 index 0000000..6ee59e2 --- /dev/null +++ b/src/simulator/mod.rs @@ -0,0 +1,659 @@ +pub mod machine; +pub mod decode; +pub mod print; +pub mod mem_cmp; +pub mod loader; + +pub mod global { + + #![allow(dead_code)] + #![allow(unused_variables)] + // Instructions type: + // - R: Register / register + // - I: Immediate + // - U: Upper-Immediate + // - S: Store + // - B: Branch (conditional branches...) + // - J: Jump + + // xlen = i64::MAX_VALUE on rv64 and i32::MAX_VALUE on rv32 + + /// Type: U + /// + /// Load upper immediate + /// + /// `LUI rd, imm31_12` => `rd <- imm31_12 << 12` + /// + pub const RISCV_LUI: u8 = 0x37; + /// Type: U + /// + /// Add upper immediate to PC + /// + /// `AUIP rd, imm31_12` => `rd <- PC + imm31_12 << 12` + pub const RISCV_AUIPC: u8 = 0x17; + /// Type: J + /// + /// Jump and link + /// + /// `JAL rd, imm20(rs1)` => `rd <- pc + 4; pc <- rs1 + imm12` + pub const RISCV_JAL: u8 = 0x6f; + /// type: J + /// + /// Jump and link register + /// + /// `JALR rd, imm12(rs1)` => `rd <- pc + 4; pc <- rs1 + imm12` + pub const RISCV_JALR: u8 = 0x67; + pub const RISCV_BR: u8 = 0x63; + + /// Load instructions + /// + /// See func3 to know the type of instruction (LD, LW, LH, LB, LWU, LHU, LBU) + pub const RISCV_LD: u8 = 0x3; + // Store instructions + pub const RISCV_ST: u8 = 0x23; + // immediate Arithmetic operations + pub const RISCV_OPI: u8 = 0x13; + // Arithmetic operations + pub const RISCV_OP: u8 = 0x33; + /// Immediate arithmetic operations for rv64i + pub const RISCV_OPIW: u8 = 0x1b; + // Arithmetic operations for rv64i + pub const RISCV_OPW: u8 = 0x3b; + + /// Type: B + /// + /// Branch equal + /// + /// `BEQ rs1, rs2, imm12` => `if rs1 = rs2 then pc <- pc + imm12` + pub const RISCV_BR_BEQ: u8 = 0x0; + /// Type: B + /// + /// Branch not equal + /// + /// `BNE rs1, rs2, imm12` => `if rs1 != rs2 then pc <- pc + imm12` + pub const RISCV_BR_BNE: u8 = 0x1; + /// Type: B + /// + /// Branch less than + /// + /// `BLT rs1, rs2, imm12` => `if rs1 < rs2 then pc <- pc + imm12` + pub const RISCV_BR_BLT: u8 = 0x4; + /// Type: B + /// + /// Branch greater than or equal + /// + /// `BGE rs1, rs2, imm12` => `if rs1 >= rs2 then pc <- pc + imm12` + pub const RISCV_BR_BGE: u8 = 0x5; + /// Type: B + /// + /// Branch less than unsigned + /// + /// Same as BLT but for unsigned values + pub const RISCV_BR_BLTU: u8 = 0x6; + /// Type: B + /// + /// Greater than or equal unsigned + /// + /// Same as BGE but for unsigned values + pub const RISCV_BR_BGEU: u8 = 0x7; + + /// Type: I + /// + /// Load byte (8 bits word) + /// + /// `LB rd, imm12(rs1)` => `rd <- mem[rs1 + imm12]` + pub const RISCV_LD_LB: u8 = 0x0; + /// Type: I + /// + /// Load halfword (16 bits word) + /// + /// `LH rd, imm12(rs1)` => `rd <- mem[rs1 + imm12]` + pub const RISCV_LD_LH: u8 = 0x1; + /// Type: I + /// + /// Load word (32 bits word) + /// + /// `LW rd, imm12(rs1)` => `rd <- mem[rs1 + imm12]` + pub const RISCV_LD_LW: u8 = 0x2; + /// Type: I + /// + /// Load doubleword (64-bits word) + /// + /// `LD rd, imm12(rs1)` => `rd <- mem[rs1 + imm12]` + pub const RISCV_LD_LD: u8 = 0x3; + /// Type: I + /// + /// Load byte unsigned + /// + /// `LBU rd, imm12(rs1)` => `rd <- mem[rs1 + imm12]` + pub const RISCV_LD_LBU: u8 = 0x4; + /// Type: I + /// + /// Load halfword unsigned + /// + /// `LHU rd, imm12(rs1)` => `rd <- mem[rs1 + imm12]` + pub const RISCV_LD_LHU: u8 = 0x5; + /// Type: I + /// + /// Load word unsigned (64 bits word) + /// + /// `LW rd, imm12(rs1)` => `rd <- mem[rs1 + imm12]` + pub const RISCV_LD_LWU: u8 = 0x6; + + /// Type: S + /// + /// Store halfword (SH) (16 bits) + /// + /// In case of overflow (rs2 is a 64 bits reg), only the first 16 bits values are stored + /// + /// `SH rs2, imm12(rs1)` => `rs2 -> mem[rs1 + imm12]` + pub const RISCV_ST_STH: u8 = 0x1; + /// Type: S + /// + /// Store word (SW) (32 bits) + /// + /// In case of overflow (rs2 is a 64 bits reg), only the first 32 bits values are stored + /// + /// `SW rs2, imm12(rs1)` => `rs2 -> mem[rs1 + imm12]` + pub const RISCV_ST_STW: u8 = 0x2; + /// Type: S + /// + /// Store byte (SB) (8 bits) + /// + /// In case of overflow (rs2 is a 64 bits reg), only the first 8 bits values are stored + /// + /// `SB rs2, imm12(rs1)` => `rs2 -> mem[rs1 + imm12]` + pub const RISCV_ST_STB: u8 = 0x0; + /// Type: S + /// + /// Store doubleword (SD) (64 bits) + /// + /// `SD rs2, imm12(rs1)` => `rs2 -> mem[rs1 + imm12]` + pub const RISCV_ST_STD: u8 = 0x3; + + /// Type: I + /// + /// Add immediate + /// + /// `addi rd, rs1, imm12` => `rd <- rs1 + imm12` + pub const RISCV_OPI_ADDI: u8 = 0x0; + /// Type: I + /// + /// Set less than immediate: set rd to 1 if rs1 < imm12, 0 otherwise + /// + /// `SLT rd, rs1, imm12` => `rd <- rs1 < imm12 ? 1 : 0` + pub const RISCV_OPI_SLTI: u8 = 0x2; + /// Type: I + /// + /// Set less than immediate unsigned : same than SLTI but for unsigned values + pub const RISCV_OPI_SLTIU: u8 = 0x3; + /// Type: I + /// + /// XOR immediate instruction + /// + /// `XORI rd, rs1, imm12` => `rd <- rs1 ^ imm12` + pub const RISCV_OPI_XORI: u8 = 0x4; + /// Type: I + /// + /// OR immediate instruction + /// + /// `ORI rd, rs1, imm12` => `rd <- rs1 | imm12` + pub const RISCV_OPI_ORI: u8 = 0x6; + /// Type: I + /// + /// AND immediate instruction + /// + /// `ANDI rd, rs1, imm12` => `rd <- rs1 & imm12` + pub const RISCV_OPI_ANDI: u8 = 0x7; + /// Type: I + /// + /// Shift left logical immediate + /// + /// `SLLI rd, rs1, shamt` => `rd <- rs1 >> shamt` + pub const RISCV_OPI_SLLI: u8 = 0x1; + /// Shift right immediate, may be SRAI or SRLI + pub const RISCV_OPI_SRI: u8 = 0x5; + /// type: I + /// + /// Shift right arithmetic immediate + /// + /// `SRAI rd, rs1, shamt` => `rd <- rs1 >> shamt` + pub const RISCV_OPI_SRI_SRAI: u8 = 0x20; + /// type: I + /// + /// Shift right logical immediate + /// + /// `SRLI rd, rs1, shamt` => `rd <- rs1 >> shamt` + pub const RISCV_OPI_SRI_SRLI: u8 = 0x0; + + /// Type: R + /// + /// Add or sub (see RISCV_OP_ADD_ADD or RISCV_OP_ADD_SUB) depending of func7 value + pub const RISCV_OP_ADD: u8 = 0x0; + /// Type: R + /// + /// Shift left logical, add a 0 on right of the word + /// + /// `SLL rd, rs1, rs2` => `rs <- rs1 << rs2` + pub const RISCV_OP_SLL: u8 = 0x1; + /// Type: R + /// + /// Set less than : set rd to 1 if rs1 < rs2, 0 otherwise + /// + /// `SLT rd, rs1, rs2` => `rd <- rs1 < rs2 ? 1 : 0` + pub const RISCV_OP_SLT: u8 = 0x2; + /// Type: R + /// + /// Set less than unsigned : same than SLT but for unsigned values + pub const RISCV_OP_SLTU: u8 = 0x3; + /// Type: R + /// + /// XOR instruction + /// + /// `XOR rd, rs1, rs2` => `rd <- rs1 ^ rs2` + pub const RISCV_OP_XOR: u8 = 0x4; + pub const RISCV_OP_SR: u8 = 0x5; + /// Type: R + /// + /// OR instruction + /// + /// `OR rd, rs1, rs2` => `rd <- rs1 | rs2` + pub const RISCV_OP_OR: u8 = 0x6; + /// Type: R + /// + /// AND instruction + /// + /// `AND rd, rs1, rs2` => `rd <- rs1 & rs2` + pub const RISCV_OP_AND: u8 = 0x7; + /// Type : R + /// + /// Addition + /// + /// `ADD rd, rs1, rs2` => `rd <- rs1 + rs2` + pub const RISCV_OP_ADD_ADD: u8 = 0x0; + /// Type: R + /// + /// Substract + /// + /// `SUB rd, rs1, rs2` => `rd <- rs1 - rs2` + pub const RISCV_OP_ADD_SUB: u8 = 0x20; + /// Type: R + /// + /// Shift right logical, add a 0 at the left of the word (should not used for signed values in most cases) + /// + /// `SRL rd, rs1, rs2` => `rd <- rs1 >> rs2` + pub const RISCV_OP_SR_SRL: u8 = 0x0; + /// Type: R + /// + /// Shift right arithmetic, add a 1 at the left of the word(useful for signed values bacause it keep signed value) + /// + /// `SRA rd, rs1, rs2` => `rd <- rs1 >> rs2` + pub const RISCV_OP_SR_SRA: u8 = 0x20; + + // ECALL or EBREAK from base instructions + /// or instructions from Ricsr extension + pub const RISCV_SYSTEM: u8 = 0x73; + + /// Type: I + /// + /// Add immediate word (RV64I only) + /// + /// `ADDIW rd, rs1, imm12` => `rd <- rs1 + imm12` + pub const RISCV_OPIW_ADDIW: u8 = 0x0; + /// Type: I + /// + /// Shift right logical immediate word (RV64I only) + /// + /// `SLLIW rd, rs1, imm12` => `rd <- rs1 >> shamt` + pub const RISCV_OPIW_SLLIW: u8 = 0x1; + + /// Shift right immediate instructions (logical or arithmetic depend of func7) + pub const RISCV_OPIW_SRW: u8 = 0x5; + /// Type: I + /// + /// Shift right logical immediate word (RV64I only) + /// + /// `SRLIW rd, rs1, imm12` => `rd <- rs1 >> shamt` + /// + /// Complete left bits by a zero, should be used with an unsigned value in most case + pub const RISCV_OPIW_SRW_SRLIW: u8 = 0x0; + /// Type: I + /// + /// Shift right arithmetic immediate word (RV64I only) + /// + /// `SRAIW rd, rs1, imm12` => `rd <- rs1 >> shamt` + /// + /// Keep sign bit + pub const RISCV_OPIW_SRW_SRAIW: u8 = 0x20; + + // ADD or SUB immediate instructions, depend of func7 value + pub const RISCV_OPW_ADDSUBW: u8 = 0x0; + /// Type: R + /// + /// Shift left logical word (RV64I only) + /// + /// `SLLW rd, rs1, rs2` => `rd <- rs1 << rs2` + pub const RISCV_OPW_SLLW: u8 = 0x1; + /// Shift right word instructions (logical or arithmetic depend of func3) + pub const RISCV_OPW_SRW: u8 = 0x5; + /// Type: R + /// + /// Add word (rv64I only) + /// + /// `ADDW rd, rs1, rs2` => `rd <- rs1 + rs2` + pub const RISCV_OPW_ADDSUBW_ADDW: u8 = 0x0; + /// Type: R + /// + /// Subtract word (rv64I only) + /// + /// `SUBW rd, rs1, rs2` => `rd <- rs1 - rs2` + pub const RISCV_OPW_ADDSUBW_SUBW: u8 = 0x20; + /// Type: R + /// + /// Shift right logical word (rv64I only) + /// + /// rd <- rs1 >> rs2 + /// + /// Complete left bits by a 0, should be used with an unsigned value + pub const RISCV_OPW_SRW_SRLW: u8 = 0x0; + /// Type: R + /// + /// Shift right arithmetic word (rv64I only) + /// + /// `SRAW rd, rs1, rs2` => `rd <- rs1 >> rs2` + /// + /// Keep sign bit + pub const RISCV_OPW_SRW_SRAW: u8 = 0x20; + + pub const RISCV_SYSTEM_ENV: u8 = 0x0; + pub const RISCV_SYSTEM_ENV_ECALL: u8 = 0x0; + pub const RISCV_SYSTEM_ENV_EBREAK: u8 = 0x1; + pub const RISCV_SYSTEM_CSRRS: u8 = 0x2; + pub const RISCV_SYSTEM_CSRRW: u8 = 0x1; + pub const RISCV_SYSTEM_CSRRC: u8 = 0x3; + pub const RISCV_SYSTEM_CSRRWI: u8 = 0x5; + pub const RISCV_SYSTEM_CSRRSI: u8 = 0x6; + pub const RISCV_SYSTEM_CSRRCI: u8 = 0x7; + + pub const RISCV_FLW: u8 = 0x07; + pub const RISCV_FSW: u8 = 0x27; + pub const RISCV_FMADD: u8 = 0x43; + pub const RISCV_FMSUB: u8 = 0x47; + pub const RISCV_FNMSUB: u8 = 0x4b; + pub const RISCV_FNMADD: u8 = 0x4f; + + /// Simple floating point extension + pub const RISCV_FP: u8 = 0x53; + + /// Type: R + /// + /// Simple precision floating point addition + /// + /// `FADD.S rd, rs1, rs2` => `rd <- rs1 + rs2` + pub const RISCV_FP_ADD: u8 = 0x0; + /// Type: R + /// + /// Simple precision floating point substraction + /// + /// `FSUB.S rd, rs1, rs2` => `rd <- rs1 - rs2` + pub const RISCV_FP_SUB: u8 = 0x4; + /// Type: R + /// + /// Simple precision floating point multiplication + /// + /// `fmul.s rd, rs1, rs2` => `rd <- rs1 * rs2` + pub const RISCV_FP_MUL: u8 = 0x8; + /// Type : R + /// + /// Simple precision floating point division + /// + /// `fdiv.s rd, rs1, rs2` => `rd <- rs1 / rs2` + pub const RISCV_FP_DIV: u8 = 0xc; + /// Type: R + /// + /// Simple precision square root + /// + /// `fsqrt.s rd, rs1` => `rd <- sqrt(rs1)` + pub const RISCV_FP_SQRT: u8 = 0x2c; + /// FSGN instructions + pub const RISCV_FP_FSGN: u8 = 0x10; + // fmin or fmax instructions + pub const RISCV_FP_MINMAX: u8 = 0x14; + /// fcvt.w instructions + /// + /// convert fp to integer + pub const RISCV_FP_FCVTW: u8 = 0x60; + /// fmv.x.w or fclass.s instruction + pub const RISCV_FP_FMVXFCLASS: u8 = 0x70; + /// floating points comparaison instructions + pub const RISCV_FP_FCMP: u8 = 0x50; + pub const RISCV_FP_FEQS: u8 = 0x53; + /// fcvt.s instructions + /// + /// Convert integer to fp + pub const RISCV_FP_FCVTS: u8 = 0x68; + pub const RISCV_FP_FCVTDS: u8 = 0x21; + + /// Type: R + /// + /// Take all bits except sign bit from rs1. sign is rs2's sign bit + /// + /// `fsgnj.s rd, rs1, rs2` => `rd <- {rs2[31], rs1[30:0]}` + pub const RISCV_FP_FSGN_J: u8 = 0x0; + /// Type: R + /// + /// Take all bits except sign bit from rs1, sign is opposite of rs2's sign bit + /// + /// `fsgnjs.s rd, rs1, rs2` => `rd <- {rs2[31], rs[30:0]}` + pub const RISCV_FP_FSGN_JN: u8 = 0x1; + /// Type: R + /// + /// Take all bits except sign bit from rs1, sign is XOR of sign bit of rs1 and rs2 + /// + /// `fsgnjx.s rd, rs1, rs2` => `rd <- {rs1[31] ^ rs2[31], rs1[30:0]}` + pub const RISCV_FP_FSGN_JX: u8 = 0x2; + + /// Type: R + /// + /// write the smaller number between rs1 and rs2 to rd + /// + /// `fmin.s rd, rs1, rs2` => `rd <- min(rs1, rs2)` + pub const RISCV_FP_MINMAX_MIN: u8 = 0x0; + /// type: R + /// + /// Write the larger number between rs1 and rs2 to rd + /// + /// `fmax.s rd, rs1, rs2` => `rd <- max(rs1, rs2)` + pub const RISCV_FP_MINMAX_MAX: u8 = 0x1; + + /// Type: R + /// + /// Convert a floating point number in register to a signed 32-bit integer and write it in integer register + /// + /// `fcvt.w.s rd, rs1` => `rd <- rs1_f32 as i32` + /// + /// rd is integer register and rs1 is floating point register + pub const RISCV_FP_FCVTW_W: u8 = 0x0; + /// Type: R + /// + /// Convert a floating point number in register to a unsigned 32 bit integer and write it in integer register + /// + /// `fcvt.wu.s rd, rs1` => `rd <- rs1_f32 as u32` + pub const RISCV_FP_FCVTW_WU: u8 = 0x1; + + /// Type : R + /// + /// Convert signed 32 bit integer in register to a floating point number and write it in fp register + /// + /// `fcvt.s.w rd, rs1` => `rd <- rs1_s32 as f32` + pub const RISCV_FP_FCVTS_W: u8 = 0x0; + /// Type: R + /// + /// Convert unsigned 32 bit integer in register to a floating point number and write it in fp register + /// + /// `fcvt.s.wu rd, rs1` => `rd <- rs1_u32 as f32` + pub const RISCV_FP_FCVTS_WU: u8 = 0x1; + /// Type: R + /// + /// Move floating point value in register to integer register, bits value aren't modified during the process + /// + /// On rv64, the lower 32 bits of the integer register are transfered, for the upper 32 bits, values are filles with copies of the floating point number's sign bit + /// + /// `fmv.x.w rd ,rs1` => `rd[31,0] <- rs1; rd[63:32] <- rs[31]` + pub const RISCV_FP_FMVXFCLASS_FMVX: u8 = 0x0; + /// Type: R + /// + /// examine the value given in fp register rs1 and writes to integer register rd a 10 bit mask that indicates the class of the fp number. + /// Format is described here: + /// + /// | rd bit | meaning | + /// |--------|------------------------------------| + /// | 0 | rs1 is -infinite | + /// | 1 | rs1 is a negative normal number | + /// | 2 | rs1 is a negative subnormal number | + /// | 3 | rs1 is -0 | + /// | 4 | rs1 is +0 | + /// | 5 | rs1 is a positive subnormal number | + /// | 6 | rs1 is a positive normal number | + /// | 7 | rs1 is +infinite | + /// | 8 | rs1 is a signaling NaN | + /// | 9 | rs1 is a quiet NaN | + /// + /// All others bit in rd are cleared + pub const RISCV_FP_FMVXFCLASS_FCLASS: u8 = 0x1; + + /// Type: R + /// + /// Quiet equal comparaison, NaN cause an invalid operation exception + /// + /// `feq.s rd, rs1, rs2` => `rd <- rs1 == rs2` + pub const RISCV_FP_FCMP_FEQ: u8 = 2; + /// Type: R + /// + /// Quiet less comparaison, NaN cause an invalid operation exception + /// + /// `flt.s rd, rs1, rs2` => `rdf <- rs1 < rs2` + pub const RISCV_FP_FCMP_FLT: u8 = 1; + /// Type: R + /// + /// Quiet less or equal comparaison, NaN cause an invalid operation exception + /// + /// `fle.s rd, rs1, rs2` => `rd <- rs1 <= rs2` + pub const RISCV_FP_FCMP_FLE: u8 = 0; + + /// Type : R + /// + /// Move floating point value in integer register to the fp register. Bits aren't modified in the transfer + /// + /// On rv64, only the lower 32 bits in the integer register are transfered. + /// + /// `fmv.w.x rd, rs1` => `rd <- rs1[31:0]` + pub const RISCV_FP_FMVW: u8 = 0x78; + + /// Integer, multiplication and division extension + pub const RISCV_OP_M: u8 = 0x1; + + /// Type: R + /// + /// Multiply + /// + /// `MUL rd, rs1, rs2` => `rd <- rs1 * rs2` + pub const RISCV_OP_M_MUL: u8 = 0x0; + /// Type: R + /// + /// Multiply high signed signed + /// + /// `MULH rd, rs1, rs2` => `rd <- (rs1 * rs2) >> xlen` + /// + /// rs1 and rs2 signed + pub const RISCV_OP_M_MULH: u8 = 0x1; + /// Type: R + /// + /// Multiply high signed unsigned + /// + /// `MULHSU rd, rs1, rs2` => `rd <- (rs1 x rs2) >> xlen` + /// + /// rs1 is signed and rs2 is unsigned + pub const RISCV_OP_M_MULHSU: u8 = 0x2; + /// Type: R + /// + /// Multiply high unsigned unsigned + /// + /// `MULHU rd, rs1, rs2` => `rd <- (rs1 × rs2) >> xlen` + /// + /// rs1 and rs2 unsigned + pub const RISCV_OP_M_MULHU: u8 = 0x3; + /// Type: R + /// + /// Divide signed + /// + /// `DIV rd, rs1, rs2` => `rd <- r1 / rs2` + pub const RISCV_OP_M_DIV: u8 = 0x4; + /// Type: R + /// + /// Divide unsigned + /// + /// `DIVU rd, rs1, rs2` => `rd <- rs1 / rs2` + pub const RISCV_OP_M_DIVU: u8 = 0x5; + /// Type: R + /// + /// Remainder signed + /// + /// `REM rd, rs1, rs2` => `rd <- rs1 % rs2` + pub const RISCV_OP_M_REM: u8 = 0x6; + /// Type: R + /// + /// Remaindder unsigned + /// + /// `REMU rd, rs1, rs2` => `rd <- rs1 % rs2` + pub const RISCV_OP_M_REMU: u8 = 0x7; + + /// Type: R + /// + /// Multiply Word (rv64M only) + /// + /// `MULW rd, rs1, rs2` => `rd <- rs1 * rs2` + pub const RISCV_OPW_M_MULW: u8 = 0x0; + /// Type: R + /// + /// Divide signed word (RV64M only) + /// + /// `DIVW rd, rs1, rs2` => `rd <- rs1 / rs2` + pub const RISCV_OPW_M_DIVW: u8 = 0x4; + /// Type: R + /// + /// Divide unsigned word + /// + /// `DIVUW rd, rs1, rs2` => `red <- rs1 / rs2` + pub const RISCV_OPW_M_DIVUW: u8 = 0x5; + /// Type: R + /// + /// Remainder signed word (RV64M only) + /// + /// `REMW rd, rs1, rs2` => `rd <- rs1 % rs2` + pub const RISCV_OPW_M_REMW: u8 = 0x6; + /// Type: R + /// + /// Remainder unsigned word (RV64M only) + /// + /// `REMUW rd, rs1, rs2` => `rd <- rs1 % rs2` + pub const RISCV_OPW_M_REMUW: u8 = 0x7; + + /// Instruction from Zifencei extension + pub const RISCV_FENCE: u8 = 0x0f; + + /// Atomic instructions extension + pub const RISCV_ATOM: u8 = 0x2f; + pub const RISCV_ATOM_LR: u8 = 0x2; + pub const RISCV_ATOM_SC: u8 = 0x3; + pub const RISCV_ATOM_SWAP: u8 = 0x1; + pub const RISCV_ATOM_ADD: u8 = 0; + pub const RISCV_ATOM_XOR: u8 = 0x4; + pub const RISCV_ATOM_AND: u8 = 0xc; + pub const RISCV_ATOM_OR: u8 = 0x8; + pub const RISCV_ATOM_MIN: u8 = 0x10; + pub const RISCV_ATOM_MAX: u8 = 0x14; + pub const RISCV_ATOM_MINU: u8 = 0x18; + pub const RISCV_ATOM_MAXU: u8 = 0x1c; + +} \ No newline at end of file diff --git a/src/simulator/print.rs b/src/simulator/print.rs new file mode 100644 index 0000000..54bcddf --- /dev/null +++ b/src/simulator/print.rs @@ -0,0 +1,414 @@ +#![allow(dead_code)] +use super::decode::{Instruction}; +use super::global::*; + +const NAMES_OP: [&str; 8] = ["add", "sll", "slt", "sltu", "xor", "sr", "or", "and"]; +const NAMES_OPI: [&str; 8] = ["addi", "slli", "slti", "sltiu", "xori", "slri", "ori", "andi"]; +const NAMES_MUL: [&str; 8] = ["mul", "mulh", "mulhsu", "mulhu", "div", "divu", "rem", "remu"]; +const NAMES_BR: [&str; 8] = ["beq", "bne", "", "", "blt", "bge", "bltu", "bgeu"]; +const NAMES_ST: [&str; 4] = ["sb", "sh", "sw", "sd"]; +const NAMES_LD: [&str; 7] = ["lb", "lh", "lw", "ld", "lbu", "lhu", "lwu"]; +const NAMES_OPW: [&str; 8] = ["addw", "sllw", "", "", "", "srw", "", ""]; +const NAMES_OPIW: [&str; 8] = ["addiw", "slliw", "", "", "", "sri", "", ""]; + + +// Register name mapping +pub const REG_X: [&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"]; + +const REG_F: [&str; 32] = ["ft0", "ft1", "ft2", "ft3", "ft4", "ft5", "ft6", "ft7", "fs0", "fs1", +"fa0", "fa1", "fa2", "fa3", "fa4", "fa5", "fa6", "fa7", +"fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", "fs10", "fs11", +"ft8", "ft9", "ft10", "ft11"]; + + +pub fn print(ins: Instruction, pc: i32) -> String { //TODO pc should be u64 + let rd = ins.rd as usize; + let rs1 = ins.rs1 as usize; + let rs2 = ins.rs2 as usize; + let rs3 = ins.rs3 as usize; + + match ins.opcode { + RISCV_OP => { + let name: &str; + if ins.funct7 == 1 { // Use mul array + name = NAMES_MUL[ins.funct3 as usize] + } else if ins.funct3 == RISCV_OP_ADD { + // Add or Sub + if ins.funct7 == RISCV_OP_ADD_ADD { + name = "add"; + } else { + name = "sub"; + } + } else if ins.funct3 == RISCV_OP_SR { + // Srl or Sra + if ins.funct7 == RISCV_OP_SR_SRL { + name = "srl"; + } else { + name = "sra"; + } + } else { + name = NAMES_OP[ins.funct3 as usize]; + } + format!("{}\t{},{},{}", name, REG_X[rd], REG_X[rs1], REG_X[rs2]) + }, + RISCV_OPI => { + // SHAMT OR IMM + if ins.funct3 == RISCV_OPI_SRI { + if ins.funct7 == RISCV_OPI_SRI_SRLI { + format!("srli\t{},{},{}", REG_X[rd], REG_X[rs1], ins.shamt) + } else { + format!("srai\t{},{},{}", REG_X[rd], REG_X[rs1], ins.shamt) + } + } else if ins.funct3 == RISCV_OPI_SLLI { + format!("{}\t{},{},{}", NAMES_OPI[ins.funct3 as usize], REG_X[rd], REG_X[rs1], ins.shamt) + } else { + format!("{}\t{},{},{}", NAMES_OPI[ins.funct3 as usize], REG_X[rd], REG_X[rs1], ins.imm12_I_signed) + } + }, + RISCV_LUI => { + format!("lui\t{},{:x}", REG_X[rd], ins.imm31_12) + }, + RISCV_AUIPC => { + format!("auipc\t{},{:x}", REG_X[rd], ins.imm31_12) + }, + RISCV_JAL => { + format!("jal\t{},{:x}", REG_X[rd], (pc + ins.imm21_1_signed)) + }, + RISCV_JALR => { + format!("jalr\t{},{:x}({})", REG_X[rd], ins.imm12_I_signed, REG_X[rs1]) + }, + RISCV_BR => { + format!("{}\t{},{},{:x}", NAMES_BR[ins.funct3 as usize], REG_X[rs1], REG_X[rs2], pc + (ins.imm13_signed as i32)) + }, + RISCV_LD => { + format!("{}\t{},{}({})", NAMES_LD[ins.funct3 as usize], REG_X[rd], ins.imm12_I_signed, REG_X[rs1]) + }, + RISCV_ST => { + format!("{}\t{},{}({})", NAMES_ST[ins.funct3 as usize], REG_X[rs2], ins.imm12_S_signed, REG_X[rs1]) + }, + RISCV_OPIW => { + if ins.funct3 == RISCV_OPIW_SRW { + if ins.funct7 == RISCV_OPIW_SRW_SRLIW { + format!("srliw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2]) + } else { + format!("sraiw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2]) + } + } else { + format!("{}\t{},{},0x{:x}", NAMES_OPIW[ins.funct3 as usize], REG_X[rd], REG_X[rs1], ins.imm12_I_signed) + } + }, + RISCV_OPW => { + if ins.funct7 == 1 { + format!("{}w\t{},{},{}", NAMES_MUL[ins.funct3 as usize], REG_X[rd], REG_X[rs1], REG_X[rs2]) + } else if ins.funct3 == RISCV_OP_ADD { + if ins.funct7 == RISCV_OPW_ADDSUBW_ADDW { + format!("addw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2]) + } else { + format!("subw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2]) + } + } else if ins.funct3 == RISCV_OPW_SRW { + if ins.funct7 == RISCV_OPW_SRW_SRLW { + format!("srlw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2]) + } else { + format!("sraw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2]) + } + } else { + format!("{}\t{},{},{}", NAMES_OPW[ins.funct3 as usize], REG_X[rd], REG_X[rs1], REG_X[rs2]) + } + }, + // RV32F Standard Extension + RISCV_FLW => { + format!("flw\t{},{},({})", REG_F[rd], ins.imm12_I_signed, REG_F[rs1]) + }, + RISCV_FSW => { + format!("fsw\t{},{},({})", REG_F[rs2], "OFFSET TODO", REG_F[rs1]) // TODO Offset in decode + }, + RISCV_FMADD => { + format!("fmadd\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3]) + }, + RISCV_FMSUB => { + format!("fmsub\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3]) + }, + RISCV_FNMSUB => { + format!("fnmsub\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3]) + }, + RISCV_FNMADD => { + format!("fnmadd\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3]) + }, + RISCV_FP => { + match ins.funct7 { + RISCV_FP_ADD => { + format!("{}\t{}{}{}", "fadd", REG_F[rd], REG_F[rs1], REG_F[rs2]) + }, + RISCV_FP_SUB => { + format!("{}\t{}{}{}", "fsub.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + }, + RISCV_FP_MUL => { + format!("{}\t{}{}{}", "fmul.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + }, + RISCV_FP_DIV => { + format!("{}\t{}{}{}", "fdiv.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + }, + RISCV_FP_SQRT => { + format!("{}\t{}{}", "fsqrt.s", REG_F[rd], REG_F[rs1]) + }, + RISCV_FP_FSGN => { + match ins.funct3 { + RISCV_FP_FSGN_J => { + format!("{}\t{}{}{}", "fsgnj.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + }, + RISCV_FP_FSGN_JN => { + format!("{}\t{}{}{}", "fsgnn.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + }, + RISCV_FP_FSGN_JX => { + format!("{}\t{}{}{}", "fsgnx.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + }, + _ => todo!("Unknown code") + } + }, + RISCV_FP_MINMAX => { + if ins.funct3 == 0 { + format!("{}\t{}{}{}", "fmin.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + } else { + format!("{}\t{}{}{}", "fmax.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + } + }, + RISCV_FP_FCVTW => { + if rs2 == 0 { + format!("{}\t{}{}", "fcvt.w.s", REG_F[rd], REG_F[rs1]) + } else { + format!("{}\t{}{}", "fcvt.wu.s", REG_F[rd], REG_F[rs1]) + } + }, + RISCV_FP_FMVXFCLASS => { + if ins.funct3 == 0 { + format!("{}\t{}{}", "fmv.x.w", REG_F[rd], REG_F[rs1]) + } else { + format!("{}\t{}{}", "fclass.s", REG_F[rd], REG_F[rs1]) + } + }, + RISCV_FP_FCMP => { + if ins.funct3 == 0 { + format!("{}\t{}{}{}", "fle.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + } else if ins.funct3 == 1 { + format!("{}\t{}{}{}", "flt.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + } else { + format!("{}\t{}{}{}", "feq.s", REG_F[rd], REG_F[rs1], REG_F[rs2]) + } + }, + RISCV_FP_FCVTS => { + if rs2 == 0 { + format!("{}\t{}{}", "fcvt.s.w", REG_F[rd], REG_F[rs1]) + } else { + format!("{}\t{}{}", "fcvt.s.wu", REG_F[rd], REG_F[rs1]) + } + }, + RISCV_FP_FMVW => { + format!("{}\t{}{}", "fmv.w.x", REG_F[rd], REG_F[rs1]) + }, + _ => todo!("Unknown code") + } + }, + + RISCV_SYSTEM => { + "ecall".to_string() + }, + _ => todo!("{:x} opcode non géré pc : {:x}, value : {:x}", ins.opcode, pc, ins.value) // Change todo! to panic! in the future, I put todo! because there's a lot of opcode currently not implemented + } + +} + + +#[cfg(test)] +mod test { + #![allow(clippy::unusual_byte_groupings)] + + use crate::simulator::{decode, print}; + + + #[test] + fn test_op() { + let sub = decode::decode(0b0100000_10000_10001_000_11100_0110011); + let add = decode::decode(0b0000000_10000_10001_000_11100_0110011); + let xor = decode::decode(0b0000000_10000_10001_100_11100_0110011); + let slr = decode::decode(0b0000000_10000_10001_101_11100_0110011); + let sra = decode::decode(0b0100000_10000_10001_101_11100_0110011); + + assert_eq!("sub\tt3,a7,a6", print::print(sub, 0)); + assert_eq!("xor\tt3,a7,a6", print::print(xor, 0)); + assert_eq!("srl\tt3,a7,a6", print::print(slr, 0)); + assert_eq!("sra\tt3,a7,a6", print::print(sra, 0)); + assert_eq!("add\tt3,a7,a6", print::print(add, 0)); + + } + + #[test] + fn test_opi() { + let addi = decode::decode(0b0000000000_10001_000_11100_0010011); + let slli = decode::decode(0b0000000000_10001_001_11100_0010011); + let slti = decode::decode(0b0000000000_10001_010_11100_0010011); + let sltiu = decode::decode(0b0000000000_10001_011_11100_0010011); + let xori = decode::decode(0b_0000000000010001_100_11100_0010011); + let ori = decode::decode(0b00000000000_10001_110_11100_0010011); + let andi = decode::decode(0b000000000000_10001_111_11100_0010011); + assert_eq!("andi\tt3,a7,0", print::print(andi, 0)); + assert_eq!("addi\tt3,a7,0", print::print(addi, 0)); + assert_eq!("slli\tt3,a7,0", print::print(slli, 0)); + assert_eq!("slti\tt3,a7,0", print::print(slti, 0)); + assert_eq!("sltiu\tt3,a7,0", print::print(sltiu, 0)); + assert_eq!("xori\tt3,a7,0", print::print(xori, 0)); + assert_eq!("ori\tt3,a7,0", print::print(ori, 0)); + } + + #[test] + fn test_lui() { + let lui = decode::decode(0b01110001000011111000_11100_0110111); + let lui_negatif = decode::decode(0b11110001000011111000_11100_0110111); + assert_eq!("lui\tt3,710f8000", print::print(lui, 0)); + assert_eq!("lui\tt3,f10f8000", print::print(lui_negatif, 0)); + } + + #[test] + fn test_ld() { + // imm rs1 f3 rd opcode + let lb = decode::decode(0b010111110000_10001_000_11100_0000011); + let lh = decode::decode(0b010111110000_10001_001_11100_0000011); + let lw = decode::decode(0b010111110000_10001_010_11100_0000011); + let lbu = decode::decode(0b010111110000_10001_100_11100_0000011); + let lhu = decode::decode(0b010111110000_10001_101_11100_0000011); + let ld = decode::decode(0b010111110000_10001_011_11100_0000011); + let lwu = decode::decode(0b010111110000_10001_110_11100_0000011); + + assert_eq!("lb\tt3,1520(a7)", print::print(lb, 0)); + assert_eq!("lh\tt3,1520(a7)", print::print(lh, 0)); + assert_eq!("lw\tt3,1520(a7)", print::print(lw, 0)); + assert_eq!("lbu\tt3,1520(a7)", print::print(lbu, 0)); + assert_eq!("lhu\tt3,1520(a7)", print::print(lhu, 0)); + assert_eq!("ld\tt3,1520(a7)", print::print(ld, 0)); + assert_eq!("lwu\tt3,1520(a7)", print::print(lwu, 0)); + } + + #[test] + fn test_opw() { + let addw: decode::Instruction = decode::decode(0b0000000_10000_10001_000_11100_0111011); + let sllw: decode::Instruction = decode::decode(0b0000000_10000_10001_001_11100_0111011); + let srlw: decode::Instruction = decode::decode(0b0000000_10000_10001_101_11100_0111011); + let sraw: decode::Instruction = decode::decode(0b0100000_10000_10001_101_11100_0111011); + + assert_eq!("addw\tt3,a7,a6", print::print(addw, 0)); + assert_eq!("sllw\tt3,a7,a6", print::print(sllw, 0)); + assert_eq!("srlw\tt3,a7,a6", print::print(srlw, 0)); + assert_eq!("sraw\tt3,a7,a6", print::print(sraw, 0)); + } + + #[test] + fn test_opwi() { + let addiw: decode::Instruction =decode::decode(0b000000000000_10001_000_11100_0011011); + let slliw: decode::Instruction = decode::decode(0b0000000_10000_10001_001_11100_0011011); + let srai: decode::Instruction = decode::decode(0b010000010001_10001_101_11100_0010011); + assert_eq!("addiw\tt3,a7,0x0", print::print(addiw, 0)); + assert_eq!("slliw\tt3,a7,0x10", print::print(slliw, 0)); + assert_eq!("srai\tt3,a7,17", print::print(srai, 0)); + + } + + #[test] + fn test_br() { + let beq: decode::Instruction = decode::decode(0b0000000_10000_10001_000_00000_1100011); + let bne: decode::Instruction = decode::decode(0b0000000_10000_10001_001_00000_1100011); + let blt: decode::Instruction = decode::decode(0b0000000_10000_10001_100_00000_1100011); + let bge: decode::Instruction = decode::decode(0b0000000_10000_10001_101_00000_1100011); + let bge2: decode::Instruction = decode::decode(0x00f75863); + let bltu: decode::Instruction = decode::decode(0b0000000_10000_10001_110_00000_1100011); + let bgeu: decode::Instruction = decode::decode(0b0000000_10000_10001_111_00000_1100011); + assert_eq!("blt\ta7,a6,0", print::print(blt, 0)); + assert_eq!("bge\ta7,a6,0", print::print(bge, 0)); + assert_eq!("bge\ta4,a5,104d4", print::print(bge2, 0x104c4)); + assert_eq!("bltu\ta7,a6,0", print::print(bltu, 0)); + assert_eq!("bgeu\ta7,a6,0", print::print(bgeu, 0)); + assert_eq!("bne\ta7,a6,0", print::print(bne, 0)); + assert_eq!("beq\ta7,a6,0", print::print(beq, 0)); + } + + #[test] + fn test_small_program() { + /* Code for : + int a = 0; + int b = 5; + a = b; + a = a * b; + a = a + b; + b = a - b; + */ + assert_eq!("addi sp,sp,-32", print::print(decode::decode(0xfe010113), 0)); + assert_eq!("sd s0,24(sp)", print::print(decode::decode(0x00813c23), 0)); + assert_eq!("addi s0,sp,32", print::print(decode::decode(0x02010413), 0)); + assert_eq!("sw zero,-20(s0)", print::print(decode::decode(0xfe042623), 0)); + assert_eq!("addi a5,zero,5", print::print(decode::decode(0x00500793), 0)); + assert_eq!("sw a5,-24(s0)", print::print(decode::decode(0xfef42423), 0)); + assert_eq!("lw a5,-24(s0)", print::print(decode::decode(0xfe842783), 0)); + assert_eq!("sw a5,-20(s0)", print::print(decode::decode(0xfef42623), 0)); + assert_eq!("lw a5,-20(s0)", print::print(decode::decode(0xfec42783), 0)); + assert_eq!("addi a4,a5,0", print::print(decode::decode(0x00078713), 0)); + assert_eq!("lw a5,-24(s0)", print::print(decode::decode(0xfe842783), 0)); + assert_eq!("mulw a5,a4,a5", print::print(decode::decode(0x02f707bb), 0)); + assert_eq!("sw a5,-20(s0)", print::print(decode::decode(0xfef42623), 0)); + assert_eq!("lw a5,-20(s0)", print::print(decode::decode(0xfec42783), 0)); + assert_eq!("addi a4,a5,0", print::print(decode::decode(0x00078713), 0)); + assert_eq!("lw a5,-24(s0)", print::print(decode::decode(0xfe842783), 0)); + assert_eq!("addw a5,a4,a5", print::print(decode::decode(0x00f707bb), 0)); + assert_eq!("sw a5,-20(s0)", print::print(decode::decode(0xfef42623), 0)); + assert_eq!("lw a5,-20(s0)", print::print(decode::decode(0xfec42783), 0)); + assert_eq!("addi a4,a5,0", print::print(decode::decode(0x00078713), 0)); + assert_eq!("lw a5,-24(s0)", print::print(decode::decode(0xfe842783), 0)); + assert_eq!("subw a5,a4,a5", print::print(decode::decode(0x40f707bb), 0)); + assert_eq!("sw a5,-24(s0)", print::print(decode::decode(0xfef42423), 0)); + assert_eq!("addi a5,zero,0", print::print(decode::decode(0x00000793), 0)); + assert_eq!("addi a0,a5,0", print::print(decode::decode(0x00078513), 0)); + assert_eq!("ld s0,24(sp)", print::print(decode::decode(0x01813403), 0)); + assert_eq!("addi sp,sp,32", print::print(decode::decode(0x02010113), 0)); + assert_eq!("jalr zero,0(ra)", print::print(decode::decode(0x00008067), 0)); + + } + + + #[test] + fn test_fibo() { + assert_eq!("jal zero,10504", print::print(decode::decode(0x0500006f), 0x104b4)); + assert_eq!("blt a4,a5,104b8", print::print(decode::decode(0xfaf740e3), 0x10518)); + } + + #[test] + fn test_mul_prog() { + assert_eq!("addi sp,sp,-32", print::print(decode::decode(0xfe010113), 0)); + assert_eq!("sd s0,24(sp)", print::print(decode::decode(0x00813c23), 0)); + assert_eq!("addi s0,sp,32", print::print(decode::decode(0x02010413), 0)); + assert_eq!("addi a5,zero,5", print::print(decode::decode(0x00500793), 0)); + assert_eq!("sw a5,-20(s0)", print::print(decode::decode(0xfef42623), 0)); + assert_eq!("lw a5,-20(s0)", print::print(decode::decode(0xfec42783), 0)); + assert_eq!("addi a4,a5,0", print::print(decode::decode(0x00078713), 0)); + assert_eq!("addi a5,a4,0", print::print(decode::decode(0x00070793), 0)); + assert_eq!("slliw a5,a5,0x2", print::print(decode::decode(0x0027979b), 0)); + assert_eq!("addw a5,a5,a4", print::print(decode::decode(0x00e787bb), 0)); + assert_eq!("sw a5,-24(s0)", print::print(decode::decode(0xfef42423), 0)); + assert_eq!("lw a5,-20(s0)", print::print(decode::decode(0xfec42783), 0)); + assert_eq!("addi a4,a5,0", print::print(decode::decode(0x00078713), 0)); + assert_eq!("lw a5,-24(s0)", print::print(decode::decode(0xfe842783), 0)); + assert_eq!("mulw a5,a4,a5", print::print(decode::decode(0x02f707bb), 0)); + assert_eq!("sw a5,-28(s0)", print::print(decode::decode(0xfef42223), 0)); + assert_eq!("lw a5,-28(s0)", print::print(decode::decode(0xfe442783), 0)); + assert_eq!("addi a4,a5,0", print::print(decode::decode(0x00078713), 0)); + assert_eq!("lw a5,-24(s0)", print::print(decode::decode(0xfe842783), 0)); + assert_eq!("divw a5,a4,a5", print::print(decode::decode(0x02f747bb), 0)); + assert_eq!("sw a5,-20(s0)", print::print(decode::decode(0xfef42623), 0)); + assert_eq!("addi a5,zero,0", print::print(decode::decode(0x00000793), 0)); + assert_eq!("addi a0,a5,0", print::print(decode::decode(0x00078513), 0)); + assert_eq!("ld s0,24(sp)", print::print(decode::decode(0x01813403), 0)); + assert_eq!("addi sp,sp,32", print::print(decode::decode(0x02010113), 0)); + assert_eq!("jalr zero,0(ra)", print::print(decode::decode(0x00008067), 0)); + } + +} \ No newline at end of file diff --git a/test_programs/.gitignore b/test_programs/.gitignore new file mode 100644 index 0000000..f7e49da --- /dev/null +++ b/test_programs/.gitignore @@ -0,0 +1,4 @@ +# Ignoring dump files +*.dump +*.o +target \ No newline at end of file diff --git a/test_programs/Makefile b/test_programs/Makefile new file mode 100644 index 0000000..ad2c039 --- /dev/null +++ b/test_programs/Makefile @@ -0,0 +1,21 @@ +TOPDIR=. +include $(TOPDIR)/Makefile.config + +# +# Main targets +# +dumps: + $(MAKE) dumps -C riscv_instructions/ + mkdir -p ${TOPDIR}/target/dumps/ + find . -name '*.dump' -exec mv {} ${TOPDIR}/target/dumps/ \; + +user_lib: + $(MAKE) -C userlib/ + +tests: user_lib + $(MAKE) tests -C riscv_instructions/ + mkdir -p ${TOPDIR}/target/guac/ + find . -name '*.guac' -exec mv {} ${TOPDIR}/target/guac/ \; + +clean: + rm -rf $(TOPDIR)/target \ No newline at end of file diff --git a/test_programs/Makefile.config b/test_programs/Makefile.config new file mode 100644 index 0000000..47436e8 --- /dev/null +++ b/test_programs/Makefile.config @@ -0,0 +1,19 @@ +# This is part of a GNU -*- Makefile -*-, to specify system-dependent +# parts of the Makefile enviroment. +# +# This gets included as part of the GNU-Makefile used in each of +# the subdirectories. +# +# Depending on your platform, you need to select the correct definition. + +## RISCV target compilation toolchain +RISCV_PREFIX=/opt/riscv/bin/ +RISCV_AS = $(RISCV_PREFIX)riscv64-unknown-elf-gcc -x assembler-with-cpp -march=rv64imfd +RISCV_GCC = $(RISCV_PREFIX)riscv64-unknown-elf-gcc +RISCV_LD = $(RISCV_PREFIX)riscv64-unknown-elf-ld +RISCV_OBJCOPY = $(RISCV_PREFIX)riscv64-unknown-elf-objcopy +DUMP_FORMAT = ihex +RISCV_ASFLAGS = $(RISCV_CPPFLAGS) +RISCV_CPPFLAGS = #nil +RISCV_CFLAGS = -Wall $(RISCV_CPPFLAGS) -march=rv64imfd +RISCV_LDFLAGS = #nil \ No newline at end of file diff --git a/test_programs/Makefile.tests b/test_programs/Makefile.tests new file mode 100644 index 0000000..681d31f --- /dev/null +++ b/test_programs/Makefile.tests @@ -0,0 +1,38 @@ +include $(TOPDIR)/Makefile.config +USERLIB = $(TOPDIR)/userlib +INCPATH += -I$(TOPDIR) -I$(USERLIB) +LDFLAGS = $(RISCV_LDFLAGS) -T $(USERLIB)/ldscript.lds +ASFLAGS = $(RISCV_ASFLAGS) $(INCPATH) +CFLAGS = $(RISCV_CFLAGS) $(INCPATH) + +# Rules +%.o: %.s + $(RISCV_AS) $(ASFLAGS) -c $< + +%.o: %.c + $(RISCV_GCC) $(CFLAGS) -c $< + +%.dump: %.o + $(RISCV_OBJCOPY) -j .text -O $(DUMP_FORMAT) $< $@ + +%.guac: %.o + $(RISCV_LD) $(LDFLAGS) $+ -o $@ + +# Dependencies +.%.d: %.s + @echo Generating dependencies for $< + @$(SHELL) -ec '$(GCC) -x assembler-with-cpp -M $(ASFLAGS) $< \ + | sed '\''s/\($*\)\.o[ :]*/\1.o $@ : /g'\'' > $@; \ + [ -s $@ ] || rm -f $@' + +.%.d: %.c + @echo Generating dependencies for $< + @$(SHELL) -ec '$(GCC) -M $(CFLAGS) $< \ + | sed '\''s/\($*\)\.o[ :]*/\1.o $@ : /g'\'' > $@; \ + [ -s $@ ] || rm -f $@' + +# Targets +#clean: +# rm -rf *.o 2> /dev/null +# rm -rf *.dump 2> /dev/null +# rm -rf *.guac 2> /dev/null \ No newline at end of file diff --git a/test_programs/README.md b/test_programs/README.md new file mode 100644 index 0000000..018ecb2 --- /dev/null +++ b/test_programs/README.md @@ -0,0 +1,28 @@ +# BurritOS Test programs +This folder contains small C programs pertaining to the assessment of the correctness of BurritOS' behavior. + +## Folder Structure + - **riscv_instructions**: contains small programs for testing simulator instruction coverage. + +## How to build +### In the Makefile.config file +Set the variables to the correct paths for the [RISCV Newlib compilation toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain). Simply changing the `RISCV_PREFIX` variable should do the trick. **Do not forget to add a trailing slash**. + +### Exporting objdumps + +``` +$ make dumps +``` + +### Compiling programs +``` +$ make programs +``` + +### Cleaning +``` +$ make clean +``` + +### Output folder +Compilation results are located in the target/objdumps and target/programs directories. \ No newline at end of file diff --git a/test_programs/riscv_instructions/Makefile b/test_programs/riscv_instructions/Makefile new file mode 100644 index 0000000..22a1303 --- /dev/null +++ b/test_programs/riscv_instructions/Makefile @@ -0,0 +1,9 @@ +dumps: + make dumps -C boolean_logic/ + make dumps -C jump_instructions/ + make dumps -C simple_arithmetics/ + +tests: + make tests -C boolean_logic/ + make tests -C jump_instructions/ + make tests -C simple_arithmetics/ \ No newline at end of file diff --git a/test_programs/riscv_instructions/boolean_logic/Makefile b/test_programs/riscv_instructions/boolean_logic/Makefile new file mode 100644 index 0000000..5f738e9 --- /dev/null +++ b/test_programs/riscv_instructions/boolean_logic/Makefile @@ -0,0 +1,9 @@ +TOPDIR = ../.. +include $(TOPDIR)/Makefile.tests + +dumps: comparisons.dump if.dump switch.dump + +tests: comparisons.guac if.guac switch.guac + +# Dependances +$(PROGRAMS): % : $(USERLIB)/sys.o $(USERLIB)/libnachos.o %.o \ No newline at end of file diff --git a/test_programs/riscv_instructions/boolean_logic/comparisons.c b/test_programs/riscv_instructions/boolean_logic/comparisons.c new file mode 100644 index 0000000..ec42461 --- /dev/null +++ b/test_programs/riscv_instructions/boolean_logic/comparisons.c @@ -0,0 +1,15 @@ +int main() { + int x = 0; + int y = 1; + while (x <= y) { + if (x > y) { + x += 1; + } else if (x == y) { + x += y; + } else if (x < y) { + y += 1; + } else { + return 0; + } + } +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/boolean_logic/if.c b/test_programs/riscv_instructions/boolean_logic/if.c new file mode 100644 index 0000000..2a11b58 --- /dev/null +++ b/test_programs/riscv_instructions/boolean_logic/if.c @@ -0,0 +1,10 @@ +int main() { + int x = 1; + if (x == 1 && x > 0) { + x = 2; + } else if (x || x == 0 ) { + x = 3; + } else { + x = 0; + } +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/boolean_logic/new_comparisons.c b/test_programs/riscv_instructions/boolean_logic/new_comparisons.c new file mode 100644 index 0000000..54bd275 --- /dev/null +++ b/test_programs/riscv_instructions/boolean_logic/new_comparisons.c @@ -0,0 +1,17 @@ +int main() { + int x = 0; + int y = 1; + while (x <= y) { + if (x > y) { + y += 1; + } + if (x == y) { + x += y; + } + if (x < y) { + x += 1; + } else { + return 0; + } + } +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/boolean_logic/switch.c b/test_programs/riscv_instructions/boolean_logic/switch.c new file mode 100644 index 0000000..4ad23b2 --- /dev/null +++ b/test_programs/riscv_instructions/boolean_logic/switch.c @@ -0,0 +1,7 @@ +int main() { + int x = 0; + switch(x) { + case 1: x = 1; break; + default: return 0; + } +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/jump_instructions/Makefile b/test_programs/riscv_instructions/jump_instructions/Makefile new file mode 100644 index 0000000..ce69447 --- /dev/null +++ b/test_programs/riscv_instructions/jump_instructions/Makefile @@ -0,0 +1,6 @@ +TOPDIR = ../.. +include $(TOPDIR)/Makefile.tests + +dumps: jump.dump ret.dump + +tests: jump.guac ret.guac \ No newline at end of file diff --git a/test_programs/riscv_instructions/jump_instructions/jump.c b/test_programs/riscv_instructions/jump_instructions/jump.c new file mode 100644 index 0000000..99f98b8 --- /dev/null +++ b/test_programs/riscv_instructions/jump_instructions/jump.c @@ -0,0 +1,8 @@ +int test() { + return 0; +} + +int main() { + int x = test(); + return x; +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/jump_instructions/ret.c b/test_programs/riscv_instructions/jump_instructions/ret.c new file mode 100644 index 0000000..84eb8ea --- /dev/null +++ b/test_programs/riscv_instructions/jump_instructions/ret.c @@ -0,0 +1,3 @@ +int main() { + return 1; +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/simple_arithmetics/Makefile b/test_programs/riscv_instructions/simple_arithmetics/Makefile new file mode 100644 index 0000000..623b297 --- /dev/null +++ b/test_programs/riscv_instructions/simple_arithmetics/Makefile @@ -0,0 +1,6 @@ +TOPDIR = ../.. +include $(TOPDIR)/Makefile.tests + +dumps: unsigned_addition.dump unsigned_division.dump unsigned_multiplication.dump unsigned_substraction.dump + +tests: unsigned_addition.guac unsigned_division.guac unsigned_multiplication.guac unsigned_substraction.guac \ No newline at end of file diff --git a/test_programs/riscv_instructions/simple_arithmetics/README.md b/test_programs/riscv_instructions/simple_arithmetics/README.md new file mode 100644 index 0000000..031c1be --- /dev/null +++ b/test_programs/riscv_instructions/simple_arithmetics/README.md @@ -0,0 +1,15 @@ +# Simple arithmetics program + +These allow to check whether the following instructions are correctly implemented. + +- addi +- sd +- sw +- li +- sw +- lw +- mv +- addw +- nop +- ld +- ret \ No newline at end of file diff --git a/test_programs/riscv_instructions/simple_arithmetics/unsigned_addition.c b/test_programs/riscv_instructions/simple_arithmetics/unsigned_addition.c new file mode 100644 index 0000000..f1c005a --- /dev/null +++ b/test_programs/riscv_instructions/simple_arithmetics/unsigned_addition.c @@ -0,0 +1,9 @@ +#include "userlib/syscall.h" +#include "userlib/libnachos.h" + +// EXPECTS TWO VARIABLES WITH A VALUE OF UNSIGNED 1 +int main() { + unsigned int x = 0; + unsigned int y = 1; + x = x + y; +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/simple_arithmetics/unsigned_division.c b/test_programs/riscv_instructions/simple_arithmetics/unsigned_division.c new file mode 100644 index 0000000..8e71c25 --- /dev/null +++ b/test_programs/riscv_instructions/simple_arithmetics/unsigned_division.c @@ -0,0 +1,6 @@ +// Expecting two variables with a value of two +int main() { + unsigned int x = 4; + unsigned int y = 2; + x = x / y; +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/simple_arithmetics/unsigned_multiplication.c b/test_programs/riscv_instructions/simple_arithmetics/unsigned_multiplication.c new file mode 100644 index 0000000..f83e4f0 --- /dev/null +++ b/test_programs/riscv_instructions/simple_arithmetics/unsigned_multiplication.c @@ -0,0 +1,6 @@ +// EXPECTS TWO VARIABLES WITH A VALUE OF UNSIGNED 2 +int main() { + unsigned int x = 1; + unsigned int y = 2; + x = x * y; +} \ No newline at end of file diff --git a/test_programs/riscv_instructions/simple_arithmetics/unsigned_substraction.c b/test_programs/riscv_instructions/simple_arithmetics/unsigned_substraction.c new file mode 100644 index 0000000..e2b7ec4 --- /dev/null +++ b/test_programs/riscv_instructions/simple_arithmetics/unsigned_substraction.c @@ -0,0 +1,6 @@ +// EXPECTS TWO VARIABLES WITH A VALUE OF UNSIGNED 1 +int main() { + unsigned int x = 1; + unsigned int y = 1; + x = x - y; +} \ No newline at end of file diff --git a/test_programs/userlib/Makefile b/test_programs/userlib/Makefile new file mode 100644 index 0000000..903f3b5 --- /dev/null +++ b/test_programs/userlib/Makefile @@ -0,0 +1,4 @@ +TOPDIR = ../ +include $(TOPDIR)/Makefile.tests + +default: sys.o libnachos.o \ No newline at end of file diff --git a/test_programs/userlib/ldscript.lds b/test_programs/userlib/ldscript.lds new file mode 100644 index 0000000..7bc9ebf --- /dev/null +++ b/test_programs/userlib/ldscript.lds @@ -0,0 +1,61 @@ +/* + ldscript for running user programs under Nachos + + Sections should be aligned on page boundaries. Here an alignement of + at least 0x2000 is selected, thus supporting pages up to 8KB + large. See addrspace.cc for details. +*/ + +ENTRY(__start) +SECTIONS +{ + + /* Skip an area of about 8k, so that NULL pointer dereferences can + be detected */ + . += 0x2000; + + .sys ALIGN(0x4000) : { + *(.init) + *(.sys) + } + + /* Code is aligned on a 16k boundary + Due to the size of the .sys section, the code start address will + presumably be at address 0x4000 */ + .text ALIGN(0x400000) : { + _ftext = .; + eprol = .; + *(.text) + *(.fini) + } + etext = .; + _etext = .; + + /* Initialized data is aligned on a 16k boundary */ + .data ALIGN(0x4000) : { + _fdata = .; + *(.data) + *(.sdata) + } + .rodata ALIGN(0x4000) : + { + *(.rdata) + *(.srodata) + *(.rodata) + } + edata = .; + _edata = .; + + /* Non-initialized data is aligned on a 16k boundary */ + /* Bss = Block Started by Symbol */ + .bss ALIGN(0x4000) : { + *(.bss) + *(.sbss) + *(.scommon) + *(COMMON) + } + + end = .; + _end = .; + +} diff --git a/test_programs/userlib/libnachos.c b/test_programs/userlib/libnachos.c new file mode 100644 index 0000000..32bb273 --- /dev/null +++ b/test_programs/userlib/libnachos.c @@ -0,0 +1,630 @@ +/*! \file libnachos.c + * \brief Functions of our library, for user programs. + * + * This library only provides some usefull functions for + * programming. + * + * ----------------------------------------------------- + * This file is part of the Nachos-RiscV distribution + * Copyright (c) 2022 University of Rennes 1. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details + * (see see ). + * ----------------------------------------------------- + */ + +#include "libnachos.h" +#include +#include + +//---------------------------------------------------------------------- +// threadStart() +/*! Makes a thread execute a function or program. This function +// is static, it is called internally by library function threadCreate +// and should not be called directly by user programs. The interest +// of this function is to be able to terminate threads correctly, +// even when the thread to be terminated does not explicitly call +// Exit. threadStart provides the mechanism by which Exit +// is called automatically +// +// \param func is the identificator of the function to execute. +*/ +//---------------------------------------------------------------------- + +static void threadStart(uint64_t func) +{ + VoidNoArgFunctionPtr func2; + func2=(VoidNoArgFunctionPtr)func; + // Call the function that actually contains the thread code + (*func2)(); + // Call exit, such that there is no return using an empty stack + Exit(0); +} + +//---------------------------------------------------------------------- +// threadCreate() +/*! Creates a thread and makes it execute a function. +// +// NB : instead of directly executing the required function, +// function threadStart is called so as to ensure +// that the thread will properly exit +// This function must be called instead of calling directly +// the system call newThread +// +// \param name the name of the thread (for debugging purpose) +// \param func is the address of the function to execute. +*/ +//---------------------------------------------------------------------- +ThreadId threadCreate(char *debug_name, VoidNoArgFunctionPtr func) +{ + return newThread(debug_name, (uint64_t)threadStart,(uint64_t)func); +} + +//---------------------------------------------------------------------- +// n_strcmp() +/*! String comparison +// +// \param s1 is the first string, +// \param s2 is the second one. +// \return an integer greater than, equal to, or less than 0, +// if the first string is greater than, equal to, or less than +// the the second string. +*/ +//---------------------------------------------------------------------- +int n_strcmp(const char *s1, const char *s2) +{ + int comparaison; + int fini=0; + int i=0; + while(!fini) { + if ((s1[i]==0)&&(s2[i]==0)) { + fini=1; + comparaison=0; + } + if (s1[i]s2[i]) { + fini=1; + comparaison=1; + } + i++; + } + return comparaison; +} + +//---------------------------------------------------------------------- +// n_strcpy() +/*! String copy +// +// \param dst is where the string is to be copied, +// \param src is where the string to copy is. +// \return dst, if the copy successes, 0 otherwise +*/ +//---------------------------------------------------------------------- +char *n_strcpy(char *dst, const char *src) +{ + int i=0; + int fini=0; + if ((dst!=0)&&(src!=0)) { + while(fini==0) { + if(src[i]=='\0') fini=1; + dst[i]=src[i]; + i++; + } + return dst; + } + else return 0; +} + +//---------------------------------------------------------------------- +// n_strlen() +/*! Gives the number of bytes in a string, not including the +// terminating null character. +// +// \param c is a pointer onto a string. +// \return the length of the string. +*/ +//---------------------------------------------------------------------- +size_t n_strlen(const char *s) +{ + size_t i=0; + while (s[i] != 0) i++; + return i; +} + + +//---------------------------------------------------------------------- +// n_strcat() +/*! Appends a copy of a string, including null character, to the end +// of another string. Enough memory has to be available in the +// destination string. +// +// \param dst is a pointer onto the string where the other string +// will be appended. +// \param src is the string to append. +// \return the pointer string dst. +*/ +//---------------------------------------------------------------------- +char *n_strcat(char *dst, const char *src) +{ + int i,j,k; + i=(int)n_strlen(dst); + j=(int)n_strlen(src); + for(k=i;k<=j+i;k++) { + dst[k]=src[k-i]; + } + return dst; +} + + +//---------------------------------------------------------------------- +// n_toupper() +/*! Gives the upper-case letter corresponding to the lower-case +// letter passed as parameter. +// +// \param c is the ASCII code of the letter to transform. +// \return the corresponding upper-case letter +*/ +//---------------------------------------------------------------------- +int n_toupper(int c) +{ + if((c>='a')&&(c<='z')) + return c+('A'-'a'); + else return c; +} + +//---------------------------------------------------------------------- +// n_tolower() +/*! Gives the lower-case letter corresponding to the upper-case +// letter passed as parameter +// +// \param c is the ASCII code of the letter to transform. +// \return the corresponding lower-case letter +*/ +//---------------------------------------------------------------------- +int n_tolower(int c) +{ + if((c<='Z')&&(c>='A')) + return c+('a'-'A'); + else return c; +} + +//---------------------------------------------------------------------- +// n_atoi() +/*! String to integer conversion. +// +// \param c is a pointer onto a string. +// \return the corresponding value +*/ +//---------------------------------------------------------------------- +int n_atoi(const char *str) +{ + int i=0; + int fini=0; + int val=0; + int negative = 0; + if (str[i] == '-') { + negative = 1; i=1; + } + while(!fini) + { + if(str[i]==0 || str[i]<'0' || str[i]>'9') + fini=1; + else + { + val*=10; + val+=str[i]-'0'; + i++; + } + } + if (negative) return(-val); else return val; +} + +//---------------------------------------------------------------------- +// n_memcmp() +/*! Memory comparison. +// +// \param s1 is the first memory area, +// \param s2 is the second memory area. +// \param n size in bytes of the area to be compared. +// \return an integer less than, equal to, or greater than 0, +// according as s1 is lexicographically less than, equal to, +// or greater than s2 when taken to be unsigned characters. +// +*/ +//---------------------------------------------------------------------- +int n_memcmp(const void *s1, const void *s2, size_t n) +{ + unsigned char* c1=(unsigned char*)s1; + unsigned char* c2=(unsigned char*)s2; + + int comparaison=0; + int fini=0; + int i=0; + while ((!fini)&&(ic2[i]) { + fini=1; + comparaison=1; + } + i++; + } + return comparaison; +} + +//---------------------------------------------------------------------- +// n_memcpy() +/*! Memory copy. +// +// \param s1 is where the elements are to be copied, +// \param s2 is the memory area to copy. +// \param n size in bytes of the area to be copied. +// \return the memory area where the copy has been done. +*/ +//---------------------------------------------------------------------- +void *n_memcpy(void *s1, const void *s2, size_t n) +{ + + unsigned char* c1=(unsigned char*)s1; + unsigned char* c2=(unsigned char*)s2; + + int i=0; + if ((c1!=0)&&(c2!=0)) { + while(i> 4) & 0xf); + s[1] = TOHEX(addr[i] & 0xf); + s[2] = '\0'; + n_printf("%s ", s); + if ((((i+1)%16) == 0) || (i == len-1)) + n_printf("\n"); + } +} + +#define PUTCHAR(carac) \ + do { \ + if (result < len-1) *buff++ = carac;\ + result++; \ + } while (0) + + +//---------------------------------------------------------------------- +// n_vsnprintf() +/*! Build a string according to a specified format (internal function) +// +// Nachos vsnprintf accepts: +// %c to print a character, +// %s, to print a string, +// %d, to print an integer, +// %x, to print an integer in hexa +// %lx %ld same for 64-bit values +// %f, to print a floating point value +// +// \param buff the destination buffer to generate the string to +// \param len the size of buff, determines the number max of +// characters copied to buff (taking the final \0 into account) +// \param format the string to parse +// \param ap parameters to print +// +// \return the number of characters formatted (NOT including \0), +// that is, the number of characters that would have been written +// to the buffer if it were large enough. -1 on error. +*/ +//---------------------------------------------------------------------- +static int n_vsnprintf(char *buff, int len, const char *format, va_list ap) +{ + int i, result; + + if (!buff || !format || (len < 0)) { + return -1; + } + result = 0; + + for (i=0 ; format[i] != '\0' ; i++) { + switch (format[i]) { + case '%': + i++; + switch(format[i]) { + case '%': { + PUTCHAR('%'); + break; + } + case 'i': + case'd': { + int integer = (int) va_arg(ap,int); + int cpt2 = 0; + char buff_int[11]; + if (integer<0) {PUTCHAR('-'); + } + do { + int m10 = integer%10; + m10 = (m10 < 0)? -m10:m10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + for (cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) { + PUTCHAR(buff_int[cpt2]); + } + break; + } + case 'l': { + i++; + switch(format[i]) { + case 'd': { + long longer = va_arg(ap,long); + int cpt2 = 0; + char buff_long[20]; + if (longer<0) { + PUTCHAR('-'); + } + do { + int m10 = longer%10; + m10 = (m10 < 0)? -m10:m10; + buff_long[cpt2++]=(char)('0'+ m10); + longer=longer/10; + } while(longer!=0); + for (cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) { + PUTCHAR(buff_long[cpt2]); + } + break; + } + case 'x': { + uint64_t hexa = va_arg(ap,long); + uint64_t nb; + uint32_t i, had_nonzero = 0; + for (i=0 ; i < 16 ; i++) { + nb = (hexa << (i*4)); + nb = (nb >> 60); + nb = nb & 0x000000000000000f; + // Skip the leading zeros + if (nb == 0) { + if (had_nonzero) { + PUTCHAR((uint8_t)'0'); + } + } + else { + had_nonzero = 1; + if (nb < 10) + PUTCHAR((uint8_t)'0'+(uint8_t)nb); + else + PUTCHAR((uint8_t)'a'+(uint8_t)(nb-10)); + } + } + if (! had_nonzero) + PUTCHAR((uint8_t)'0'); + break; + } + default: { + PUTCHAR('%'); + PUTCHAR('l'); + PUTCHAR(format[i]); + break; + } + } + + break; + } + case 'c': { + int value = va_arg(ap,int); + PUTCHAR((char)value); + break; + } + case 's': { + char *string = va_arg(ap,char *); + if (! string) + string = "(null)"; + for( ; *string != '\0' ; string++) + PUTCHAR(*string); + break; + } + case 'x': { + uint32_t hexa = va_arg(ap,int); + uint32_t nb; + uint32_t i, had_nonzero = 0; + for (i=0 ; i < 8 ; i++) { + nb = (hexa << (i*4)); + nb = (nb >> 28); + nb = nb & 0x0000000f; + // Skip the leading zeros + if (nb == 0) { + if (had_nonzero) + PUTCHAR((uint8_t)'0'); + } + else { + had_nonzero = 1; + if (nb < 10) + PUTCHAR((uint8_t)'0'+(uint8_t)nb); + else + PUTCHAR((uint8_t)'a'+(uint8_t)(nb-10)); + } + } + if (! had_nonzero) + PUTCHAR((uint8_t)'0'); + break; + } + /*case 'f': { + // Very simple routine to print floats as xxxx.yyyyy + // Not very good (unable to print large numbers) + // If anyone wants to re-write it, feel free ... + double f = (double) va_arg(ap,double); + int cpt2, j; + char buff_float[200]; + long ient,idec; + if (f<0) { + PUTCHAR('-'); + f = -f; + } + ient = (int)f; + // 100000 = print 5 digits max + idec = (int)((f - ((double)ient))*100000); + // Round up + if ( f - ((double)ient) - ((double)idec)/100000.0 >= 0.5E-5) + idec ++; + cpt2 = 0; + // Print digits after the '.' + for (j=0 ; j<5 ; j++) { + buff_float[cpt2++]=(char)('0'+(idec%10)); + idec=idec/10; + } + buff_float[cpt2++] = '.'; + // Print digits before the '.' + do { + buff_float[cpt2++]=(char)('0'+ (ient%10)); + ient=ient/10; + } while (ient!=0); + for(j = cpt2 - 1 ; j >= 0 ; j--) + PUTCHAR(buff_float[j]); + break; + } + */ + default: + PUTCHAR('%'); + PUTCHAR(format[i]); + } + break; + default: + PUTCHAR(format[i]); + } + } + *buff = '\0'; + return result; +} + +//---------------------------------------------------------------------- +// n_snprintf() +/*! Build a string according to a specified format +// +// Nachos snprintf accepts: +// %c to print a character, +// %s, to print a string, +// %d, to print an integer, +// %x, to print a string in hexa +// %f, to print a floating point value +// +// \param buff the destination buffer to generate the string to +// \param len the size of buff, determines the number max of +// characters copied to buff (taking the final \0 into account) +// \param format the string to parse +// \param ... the (variable number of) arguments +// +// \return the number of characters formatted (NOT including \0), +// that is, the number of characters that would have been written +// to the buffer if it were large enough. -1 on error. +*/ +//---------------------------------------------------------------------- +int n_snprintf(char * buff, int len, const char *format, ...){ + va_list ap; + va_start(ap, format); + len = n_vsnprintf(buff, len, format, ap); + va_end(ap); + return len; +} + +//---------------------------------------------------------------------- +// n_printf() +/*! Print to the standard output parameters. +// +// Nachos printf accepts: +// %c to print a character, +// %s, to print a string, +// %d, to print an integer, +// %x, to print a string in hexa +// %ld, %lx, same for 64-bit values +// %f, to print a floating point value +// +// \param parameters to print, +// \param type of print. +*/ +//---------------------------------------------------------------------- +void n_printf(const char *format, ...){ + + va_list ap; + char buff[200]; + int len; + + va_start(ap, format); + len = n_vsnprintf(buff, sizeof(buff), format, ap); + va_end(ap); + + if (len >= sizeof(buff)) { + len = sizeof(buff) - 1; + } + if (len > 0) { + Write(buff,len,CONSOLE_OUTPUT); + } +} + +//---------------------------------------------------------------------- +// n_read_int() +/*! +// Very basic minimalist read integer function, no error +// checking... +*/ +//---------------------------------------------------------------------- +int n_read_int(void) { + char buff[200]; + Read(buff,200,CONSOLE_INPUT); + return n_atoi(buff); +} diff --git a/test_programs/userlib/libnachos.h b/test_programs/userlib/libnachos.h new file mode 100644 index 0000000..f033ed5 --- /dev/null +++ b/test_programs/userlib/libnachos.h @@ -0,0 +1,87 @@ +/*! \file libnachos.h + \brief Function structures for programs + + Libnachos proposes several 'libc-like' functions + for: + Input-Output operations, + String operations, + Memory operations, + System calls are defined in kernel/syscalls.h + + Nachos-libc functions are prefixed by 'n' to avoid + any confusion with standard libc functions. + + * ----------------------------------------------------- + * This file is part of the Nachos-RiscV distribution + * Copyright (c) 2022 University of Rennes 1. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details + * (see see ). + * ----------------------------------------------------- +*/ + +#include "userlib/syscall.h" + +typedef void (*VoidNoArgFunctionPtr)(); +typedef unsigned int size_t; + +// Thread management +// ---------------------------- +ThreadId threadCreate(char * debug_name, VoidNoArgFunctionPtr func); + +// Input/Output operations : +// ------------------------------------ + +// Print on the standard output specified parameters. +void n_printf(const char *format, ...); + +// Format (of max length ) according to the format +int n_snprintf(char * buff, int len, const char *format, ...); + +// Read an integer on the standard input +int n_read_int(void); + +// String operations : +// ------------------- + +// Compare two strings byte by byte. +int n_strcmp(const char *s1, const char *s2); + +// Copy a string. +char* n_strcpy(char *dst, const char *src); + +// Return the number of bytes in a string. +size_t n_strlen(const char *s); + +// appends a copy of a string, to the end of another string. +char* n_strcat(char *dst, const char *src); + +// Return a upper-case letter, +// equivalent to the lower-case letter given. +int n_toupper(int c); + +// Return a lower-case letter, +// equivalent to the upper-case letter given. +int n_tolower(int c); + +// Convert a string in integer. +int n_atoi(const char *str); + +// Concerning memory area operations : +// ----------------------------------- + +// Compare two memory area, looking at the first n bytes . +int n_memcmp(const void *s1, const void *s2, size_t n); + +// Copy n byte from an memory area to another. +void* n_memcpy(void *s1, const void *s2, size_t n); + +// Set the first n bytes in a memory area to a specified value. +void* n_memset(void *s, int c, size_t n); diff --git a/test_programs/userlib/sys.s b/test_programs/userlib/sys.s new file mode 100644 index 0000000..64c3069 --- /dev/null +++ b/test_programs/userlib/sys.s @@ -0,0 +1,343 @@ +/* Start.s + * Assembly language assist for user programs running on top of Nachos. + * + * Since we don't want to pull in the entire C library, we define + * what we need for a user program here, namely Start and the system + * calls. + * + * ----------------------------------------------------- + * This file is part of the BurritOS distribution + * Copyright (c) 2022 University of Rennes 1. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details + * (see see ). + * ----------------------------------------------------- + */ + +#define IN_ASM +#include "userlib/syscall.h" + + // Equivalent to ".text", but with a different name, in order + // to be correctly handled by the ldscript + .section .sys,"ax",@progbits + + .align 2 + +/* ------------------------------------------------------------- + * __start + * Initialize running a C program, by calling "main". + * + * NOTE: This has to be first, so that it gets loaded at location 0. + * The Nachos kernel always starts a program by jumping to location 0. + * ------------------------------------------------------------- + */ + + .globl __start + .type __start, @function +__start: + +/* Call the program entry point */ + call main + li a0, 0 + call Exit + jr ra /* if we return from main, exit(0) */ + + +/* ------------------------------------------------------------- + * System call stubs: + * Assembly language assist to make system calls to the Nachos kernel. + * There is one stub per system call, that places the code for the + * system call into register r10, and leaves the arguments to the + * system call alone (in other words, arg1 is in r12, arg2 is + * in r13, arg3 is in r14, arg4 is in r15) + * + * The return value is in r10. This follows the standard C calling + * convention on the RISC-V. + * ------------------------------------------------------------- + */ + + .globl Halt + .type __Halt, @function +Halt: + addi a7,zero,SC_HALT + ecall + jr ra + + + .globl Exit + .type __Exit, @function +Exit: + addi a7,zero,SC_EXIT + ecall + jr ra + + + .globl Exec + .type __Exec, @function +Exec: + addi a7,zero,SC_EXEC + ecall + jr ra + + + .globl Join + .type __Join, @function +Join: + addi a7,zero,SC_JOIN + ecall + jr ra + + + .globl Create + .type __Create, @function +Create: + addi a7,zero,SC_CREATE + ecall + jr ra + + + .globl Open + .type __Open, @function +Open: + addi a7,zero,SC_OPEN + ecall + jr ra + + + .globl Read + .type __Read, @function +Read: + addi a7,zero,SC_READ + ecall + jr ra + + + + .globl Write + .type __Write, @function +Write: + addi a7,zero,SC_WRITE + ecall + jr ra + + + .globl Seek + .type __Seek, @function +Seek: + addi a7,zero,SC_SEEK + ecall + jr ra + + + .globl Close + .type __Close, @function +Close: + addi a7,zero,SC_CLOSE + ecall + jr ra + + + .globl FSList + .type __FSList, @function +FSList: + addi a7,zero,SC_FSLIST + ecall + jr ra + + + .globl newThread + .type __newThread, @function +newThread: + addi a7,zero,SC_NEW_THREAD + ecall + jr ra + + + .globl Remove + .type __Remove, @function +Remove: + addi a7,zero,SC_REMOVE + ecall + jr ra + + + .globl Yield + .type __Yield, @function +Yield: + addi a7,zero,SC_YIELD + ecall + jr ra + + + .globl PError + .type __PError, @function +PError: + addi a7,zero,SC_PERROR + ecall + jr ra + + + .globl P + .type __P, @function +P: + addi a7,zero,SC_P + ecall + jr ra + + + .globl V + .type __V, @function +V: + addi a7,zero,SC_V + ecall + jr ra + + .globl SemCreate + .type __SemCreate, @function +SemCreate: + addi a7,zero,SC_SEM_CREATE + ecall + jr ra + + + .globl SemDestroy + .type __SemDestroy, @function +SemDestroy: + addi a7,zero,SC_SEM_DESTROY + ecall + jr ra + + + .globl SysTime + .type __SysTime, @function +SysTime: + addi a7,zero,SC_SYS_TIME + ecall + jr ra + + + .globl LockCreate + .type __LockCreate, @function +LockCreate: + addi a7,zero,SC_LOCK_CREATE + ecall + jr ra + + .globl LockDestroy + .type __LockDestroy, @function +LockDestroy: + addi a7,zero,SC_LOCK_DESTROY + ecall + jr ra + + + .globl LockAcquire + .type __LockAquire, @function +LockAcquire: + addi a7,zero,SC_LOCK_ACQUIRE + ecall + jr ra + + + .globl LockRelease + .type __LockRelease, @function +LockRelease: + addi a7,zero,SC_LOCK_RELEASE + ecall + jr ra + + + .globl CondCreate + .type __CondCreate, @function +CondCreate: + addi a7,zero,SC_COND_CREATE + ecall + jr ra + + + .globl CondDestroy + .type __CondDestroy, @function +CondDestroy: + addi a7,zero,SC_COND_DESTROY + ecall + jr ra + + + .globl CondWait + .type __CondWait, @function +CondWait: + addi a7,zero,SC_COND_WAIT + ecall + jr ra + + + .globl CondSignal + .type __CondSignal, @function +CondSignal: + addi a7,zero,SC_COND_SIGNAL + ecall + jr ra + + + .globl CondBroadcast + .type __CondBroadcast, @function +CondBroadcast: + addi a7,zero,SC_COND_BROADCAST + ecall + jr ra + + + .globl TtySend + .type __TtySend, @function +TtySend: + addi a7,zero,SC_TTY_SEND + ecall + jr ra + + + .globl TtyReceive + .type __TtyReceive, @function +TtyReceive: + addi a7,zero,SC_TTY_RECEIVE + ecall + jr ra + + + .globl Mkdir + .type __Mkdir, @function +Mkdir: + addi a7,zero,SC_MKDIR + ecall + jr ra + + + .globl Rmdir + .type __Rmdir, @function +Rmdir: + addi a7,zero,SC_RMDIR + ecall + jr ra + + + .globl Mmap + .type __Mmap, @function +Mmap: + addi a7,zero,SC_MMAP + ecall + jr ra + + .globl Debug + .type __Debug, @function +Debug: + addi a7,zero,SC_DEBUG + ecall + jr ra + + diff --git a/test_programs/userlib/syscall.h b/test_programs/userlib/syscall.h new file mode 100644 index 0000000..e650db1 --- /dev/null +++ b/test_programs/userlib/syscall.h @@ -0,0 +1,287 @@ +/*! \file syscall.h + \brief Nachos system call interface. + + These are Nachos kernel operations + that can be invoked from user programs, by trapping to the kernel + via the "syscall" instruction. + + This file is included by user programs and by the Nachos kernel. + + Each of these is invoked by a user program by simply calling the + procedure; an assembly language stub stuffs the system call code + into a register, and traps to the kernel. The kernel procedures + are then invoked in the Nachos kernel, after appropriate error checking, + from the system call entry point in exception.cc. + + * ----------------------------------------------------- + * This file is part of the Nachos-RiscV distribution + * Copyright (c) 2022 University of Rennes 1. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details + * (see see ). + * ----------------------------------------------------- + */ + + +#ifndef SYSCALLS_H +#define SYSCALLS_H + +//#include "kernel/copyright.h" + +/* system call codes -- used by the stubs to tell the kernel which system call + * is being asked for + */ +#define SC_HALT 0 +#define SC_EXIT 1 +#define SC_EXEC 2 +#define SC_JOIN 3 +#define SC_CREATE 4 +#define SC_OPEN 5 +#define SC_READ 6 +#define SC_WRITE 7 +#define SC_SEEK 8 +#define SC_CLOSE 9 +#define SC_NEW_THREAD 10 +#define SC_YIELD 11 +#define SC_PERROR 12 +#define SC_P 13 +#define SC_V 14 +#define SC_SEM_CREATE 15 +#define SC_SEM_DESTROY 16 +#define SC_LOCK_CREATE 17 +#define SC_LOCK_DESTROY 18 +#define SC_LOCK_ACQUIRE 19 +#define SC_LOCK_RELEASE 20 +#define SC_COND_CREATE 21 +#define SC_COND_DESTROY 22 +#define SC_COND_WAIT 23 +#define SC_COND_SIGNAL 24 +#define SC_COND_BROADCAST 25 +#define SC_TTY_SEND 26 +#define SC_TTY_RECEIVE 27 +#define SC_MKDIR 28 +#define SC_RMDIR 29 +#define SC_REMOVE 30 +#define SC_FSLIST 31 +#define SC_SYS_TIME 32 +#define SC_MMAP 33 +#define SC_DEBUG 34 + +#ifndef IN_ASM + +/* The system call interface. These are the operations the Nachos + * kernel needs to support, to be able to run user programs. + * + */ + +typedef int t_error; + +/* Stop Nachos, and print out performance stats */ +void Halt(); + + +/* Return the time spent running Nachos */ + +/*! \brief Defines the Nachos basic time unit */ +typedef struct { + long seconds; + long nanos; +} Nachos_Time; +void SysTime(Nachos_Time *t); + +/* Address space control operations: Exit, Exec, and Join */ + +/* This user program is done (status = 0 means exited normally). */ +void Exit(int status); + +/* A unique identifier for a thread executed within a user program */ +typedef unsigned long ThreadId; + +/* Run the executable, stored in the Nachos file "name", and return the + * master thread identifier + */ +ThreadId Exec(char *name); + +/* Create a new thread in the current process + * Return thread identifier + */ +ThreadId newThread(char * debug_name, int func, int arg); + +/* Only return once the the thread "id" has finished. + */ +t_error Join(ThreadId id); + +/* Yield the CPU to another runnable thread, whether in this address space + * or not. + */ +void Yield(); + +/*! Print the last error message with the personalized one "mess" */ +void PError(char *mess); + +/* File system operations: Create, Open, Read, Write, Seek, Close + * These functions are patterned after UNIX -- files represent + * both files *and* hardware I/O devices. + * + * If this assignment is done before doing the file system assignment, + * note that the Nachos file system has a stub implementation, which + * will work for the purposes of testing out these routines. + */ + +/* A unique identifier for an open Nachos file. */ +typedef unsigned long OpenFileId; + +/* when an address space starts up, it has two open files, representing + * keyboard input and display output (in UNIX terms, stdin and stdout). + * Read and Write can be used directly on these, without first opening + * the console device. + */ +#define CONSOLE_INPUT 0 +#define CONSOLE_OUTPUT 1 + +/* Create a Nachos file, with "name" */ +t_error Create(char *name,int size); + +/* Open the Nachos file "name", and return an "OpenFileId" that can + * be used to read and write to the file. + */ +OpenFileId Open(char *name); + +/* Write "size" bytes from "buffer" to the open file. */ +t_error Write(char *buffer, int size, OpenFileId id); + +/* Read "size" bytes from the open file into "buffer". + * Return the number of bytes actually read -- if the open file isn't + * long enough, or if it is an I/O device, and there aren't enough + * characters to read, return whatever is available (for I/O devices, + * you should always wait until you can return at least one character). + */ +t_error Read(char *buffer, int size, OpenFileId id); + +/* Seek to a specified offset into an opened file */ +t_error Seek(int offset, OpenFileId id); + +#ifndef SYSDEP_H +/* Close the file, we're done reading and writing to it. */ +t_error Close(OpenFileId id); +#endif // SYSDEP_H + +/* Remove the file */ +t_error Remove(char* name); + +/******************************************************************/ +/* system calls concerning directory management */ + +/* Create a new repertory + Return a negative number if an error ocurred. +*/ +t_error Mkdir(char* name); + +/* Destroy a repertory, which must be empty. + Return a negative number if an error ocurred. +*/ +t_error Rmdir(char* name); + +/* List the content of NachOS FileSystem */ +t_error FSList(); + +/******************************************************************/ +/* User-level synchronization operations : */ + +/* System calls concerning semaphores management */ + +typedef unsigned long SemId; + +/* Create a semaphore, initialising it at count. + Return a Semid, which will enable to do operations on this + semaphore */ +SemId SemCreate(char * debug_name, int count); + +/* Destroy a semaphore identified by sema. + Return a negative number if an error occured during the destruction */ +t_error SemDestroy(SemId sema); + +/* Do the operation P() on the semaphore sema */ +t_error P(SemId sema); + +/* Do the operation V() on the semaphore sema */ +t_error V(SemId sema); + +/* System calls concerning locks management */ +typedef unsigned long LockId; + +/* Create a lock. + Return an identifier */ +LockId LockCreate(char * debug_name); + +/* Destroy a lock. + Return a negative number if an error ocurred + during the destruction. */ +t_error LockDestroy(LockId id); + +/* Do the operation Acquire on the lock id. + Return a negative number if an error ocurred. */ +t_error LockAcquire(LockId id); + +/* Do the operation Release on the lock id. + Return a negative number if an error ocurred. +*/ +t_error LockRelease(LockId id); + +/* System calls concerning conditions variables. */ +typedef unsigned long CondId; + +/* Create a new condition variable */ +CondId CondCreate(char * debug_name); + +/* Destroy a condition variable. + Return a negative number if an error ocurred. +*/ +t_error CondDestroy(CondId id); + +/* Do the operation Wait on a condition variable. + Returns a negative number if an error ocurred. +*/ +t_error CondWait(CondId cond); + +/* Do the operation Signal on a condition variable (wake up only one thread). + Return a negative number if an error ocurred. +*/ +t_error CondSignal(CondId cond); + +/* Do the operation Signal on a condition variable (wake up all threads). + Return a negative number if an error ocurred. +*/ +t_error CondBroadcast(CondId cond); + +/******************************************************************/ +/* System calls concerning serial port and console */ + +/* Send the message on the serial communication link. + Returns the number of bytes successfully sent. +*/ +int TtySend(char *mess); + +/* Wait for a message comming from the serial communication link. + The length of the buffer where the bytes will be copied is given as a parameter. + Returns the number of characters actually received. +*/ +int TtyReceive(char *mess,int length); + +/* Map an opened file in memory. Size is the size to be mapped in bytes. +*/ +void *Mmap(OpenFileId f, int size); + +/* For debug purpose +*/ +void Debug(int param); + +#endif // IN_ASM +#endif // SYSCALL_H