perilus.rs (4948B)
1 use bounded_integer::BoundedU32; 2 use std::{ffi::CString, io, path::PathBuf, slice}; 3 4 unsafe extern "C" { 5 fn perilus_init(); 6 fn perilus_eval(); 7 fn perilus_drop(); 8 fn perilus_increment_time(); 9 fn perilus_set_clock(new_clock: u32); 10 fn perilus_get_pc() -> u32; 11 fn perilus_set_pc(new_pc: u32); 12 fn perilus_set_reset(new_reset: u32); 13 fn perilus_get_control_unit_state() -> u32; 14 fn perilus_get_register_file() -> *const u32; 15 fn perilus_set_register(reg: u32, value: u32); 16 fn perilus_get_memory() -> *const u32; 17 fn perilus_set_memory(address: u32, value: u32); 18 unsafe fn start_vcd(filename: *const std::ffi::c_char); 19 unsafe fn stop_vcd(); 20 } 21 22 pub type Register = BoundedU32<0, 31>; 23 pub type MemoryIndex = BoundedU32<0, { Perilus::MEMORY_SIZE_WORDS as u32 }>; 24 25 pub struct Perilus; 26 27 impl Perilus { 28 pub const NUM_REGISTERS: usize = 32; 29 pub const MEMORY_SIZE_WORDS: usize = 1_048_576; 30 pub const MEMORY_SIZE_BYTES: usize = Self::MEMORY_SIZE_WORDS * 4; 31 32 pub fn init() -> Perilus { 33 unsafe { perilus_init() }; 34 Perilus 35 } 36 pub fn pulse_clock(&self) { 37 unsafe { 38 perilus_set_clock(0); 39 perilus_eval(); 40 perilus_increment_time(); 41 perilus_set_clock(1); 42 perilus_eval(); 43 perilus_increment_time(); 44 } 45 } 46 pub fn reset(&self) { 47 unsafe { 48 perilus_set_reset(1); 49 self.pulse_clock(); 50 perilus_set_reset(0); 51 } 52 } 53 pub fn get_pc(&self) -> u32 { 54 unsafe { perilus_get_pc() } 55 } 56 pub fn set_pc(&self, new_pc: u32) { 57 unsafe { perilus_set_pc(new_pc) }; 58 } 59 pub fn get_control_unit_state(&self) -> u32 { 60 unsafe { perilus_get_control_unit_state() } 61 } 62 pub fn get_registers(&self) -> &'static [u32] { 63 unsafe { slice::from_raw_parts(perilus_get_register_file(), Perilus::NUM_REGISTERS) } 64 } 65 pub fn set_register(&self, register: Register, value: u32) { 66 unsafe { perilus_set_register(register.get(), value) }; 67 } 68 pub fn get_memory(&self) -> &'static [u32] { 69 unsafe { slice::from_raw_parts(perilus_get_memory(), Perilus::MEMORY_SIZE_WORDS) } 70 } 71 pub fn set_memory(&self, address: u32, value: u32) { 72 unsafe { perilus_set_memory(address, value) }; 73 } 74 pub fn get_memory_byte(&self, address_bytes: u32) -> u8 { 75 let word = self.get_memory()[(address_bytes / 4) as usize]; 76 (word >> ((address_bytes % 4) * 8)) as u8 77 } 78 pub fn set_memory_byte(&self, address_bytes: u32, value: u8) { 79 let address_words = address_bytes / 4; 80 let shift = (address_bytes % 4) * 8; 81 let current = self.get_memory()[address_words as usize]; 82 let mask = !(0xffu32 << shift); 83 self.set_memory(address_words, (current & mask) | ((value as u32) << shift)); 84 } 85 pub fn memory_bytes(&self) -> Vec<u8> { 86 self.get_memory() 87 .iter() 88 .flat_map(|word| word.to_le_bytes()) 89 .collect() 90 } 91 pub fn load_bytes(&self, bytes: &[u8]) -> io::Result<()> { 92 if bytes.len() > Self::MEMORY_SIZE_BYTES { 93 return Err(io::Error::new( 94 io::ErrorKind::InvalidInput, 95 "Provided file is too large to fit in simulated memory", 96 )); 97 } 98 for (address_bytes, value) in bytes.iter().enumerate() { 99 self.set_memory_byte(address_bytes as u32, *value); 100 } 101 Ok(()) 102 } 103 pub fn load_file(&self, file: PathBuf) -> io::Result<()> { 104 self.load_bytes(&std::fs::read(file)?) 105 } 106 pub fn save_file(&self, file: PathBuf) -> io::Result<()> { 107 std::fs::write(file, self.memory_bytes()) 108 } 109 pub fn start_trace(&self, file: PathBuf) -> io::Result<()> { 110 // Ensure the parent directory exists (VCD file will be created by Verilator) 111 if let Some(parent) = file.parent() { 112 std::fs::create_dir_all(parent)?; 113 } 114 let c_string = CString::new(file.to_string_lossy().as_bytes()) 115 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; 116 unsafe { 117 start_vcd(c_string.as_ptr()); 118 } 119 Ok(()) 120 } 121 pub fn stop_trace(&self) { 122 unsafe { 123 stop_vcd(); 124 } 125 } 126 } 127 128 impl Drop for Perilus { 129 fn drop(&mut self) { 130 unsafe { perilus_drop() }; 131 } 132 } 133 134 #[cfg(test)] 135 mod tests { 136 use super::*; 137 138 #[test] 139 fn memory_roundtrip() { 140 let perilus = Perilus::init(); 141 let data = [0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03]; 142 perilus.load_bytes(&data).unwrap(); 143 assert_eq!(&perilus.memory_bytes()[..data.len()], &data); 144 145 perilus.set_memory_byte(0, 0xaa); 146 assert_eq!(perilus.get_memory_byte(0), 0xaa); 147 148 let oversized = vec![0u8; Perilus::MEMORY_SIZE_BYTES + 1]; 149 assert!(perilus.load_bytes(&oversized).is_err()); 150 } 151 }