main.rs (17487B)
1 use std::{error::Error, fmt::Display, path::PathBuf, time::Duration}; 2 3 use clap::{Parser, Subcommand}; 4 use crossterm::event::{self, Event, KeyCode, KeyEvent}; 5 use ratatui::{ 6 DefaultTerminal, Frame, 7 buffer::Buffer, 8 layout::Rect, 9 style::{Color, Style}, 10 widgets::{Paragraph, Widget}, 11 }; 12 13 use crate::perilus::{MemoryIndex, Perilus, Register}; 14 15 mod perilus; 16 17 #[derive(Parser)] 18 struct Cli { 19 #[command(subcommand)] 20 command: Option<Command>, 21 } 22 23 #[derive(Subcommand)] 24 enum Command { 25 Interactive { 26 /// File to load verbatim into memory starting at 0x0 27 file: Option<PathBuf>, 28 }, 29 Test { 30 file: PathBuf, 31 }, 32 } 33 34 enum Mode { 35 Normal, 36 EditPc, 37 EditRegister, 38 EditMemory, 39 } 40 41 #[derive(PartialEq)] 42 enum RunState { 43 Idle, 44 Step { ignore_current_state: bool }, 45 RunTo { index: u32 }, 46 Run, 47 } 48 49 impl Display for RunState { 50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 51 match self { 52 RunState::Idle => write!(f, "Idle"), 53 RunState::Step { .. } => write!(f, "Step"), 54 RunState::RunTo { index } => write!(f, "Run to 0x{:08x}", index * 4), 55 RunState::Run => write!(f, "Run"), 56 } 57 } 58 } 59 60 struct App { 61 perilus: Perilus, 62 current_pc: u32, 63 show_register_aliases: bool, 64 register_cursor: Register, 65 memory_cursor: MemoryIndex, 66 memory_top: MemoryIndex, 67 memory_bottom: MemoryIndex, 68 mode: Mode, 69 run_state: RunState, 70 exit: bool, 71 } 72 73 impl App { 74 fn new(perilus: Perilus) -> App { 75 let current_pc = perilus.get_pc(); 76 App { 77 perilus, 78 current_pc, 79 show_register_aliases: true, 80 register_cursor: Register::const_new::<0>(), 81 memory_cursor: MemoryIndex::const_new::<0>(), 82 memory_top: MemoryIndex::const_new::<0>(), 83 memory_bottom: MemoryIndex::const_new::<0>(), 84 mode: Mode::Normal, 85 run_state: RunState::Idle, 86 exit: false, 87 } 88 } 89 fn run(&mut self, terminal: &mut DefaultTerminal) -> std::io::Result<()> { 90 while !self.exit { 91 let num_visible_memory_words = terminal 92 .get_frame() 93 .area() 94 .height 95 .saturating_sub(9) 96 .saturating_mul(8); 97 self.memory_bottom = self.memory_top + num_visible_memory_words as u32; 98 if self.memory_cursor <= self.memory_top { 99 self.memory_top = self.memory_cursor; 100 self.memory_bottom = self.memory_top + num_visible_memory_words as u32; 101 } else if self.memory_cursor >= self.memory_bottom { 102 self.memory_bottom = self.memory_cursor.saturating_add(8); 103 self.memory_top = self 104 .memory_bottom 105 .saturating_sub(num_visible_memory_words as u32); 106 } 107 self.memory_top -= self.memory_top % 8; 108 self.memory_bottom -= self.memory_bottom % 8; 109 terminal.draw(|frame| self.draw(frame))?; 110 self.handle_events(self.run_state != RunState::Idle)?; 111 self.run_state = match self.run_state { 112 RunState::Idle => RunState::Idle, 113 RunState::Step { 114 ignore_current_state, 115 } => { 116 if self.perilus.get_control_unit_state() == 0 && !ignore_current_state { 117 RunState::Idle 118 } else { 119 self.perilus.pulse_clock(); 120 RunState::Step { 121 ignore_current_state: false, 122 } 123 } 124 } 125 RunState::RunTo { index } => { 126 if let Mode::EditMemory = self.mode 127 && self.current_pc != index.saturating_mul(4) 128 { 129 self.perilus.pulse_clock(); 130 RunState::RunTo { index } 131 } else { 132 RunState::Idle 133 } 134 } 135 RunState::Run => { 136 self.perilus.pulse_clock(); 137 RunState::Run 138 } 139 }; 140 if self.perilus.get_control_unit_state() == 0 { 141 self.current_pc = self.perilus.get_pc(); 142 } 143 } 144 Ok(()) 145 } 146 fn draw(&self, frame: &mut Frame) { 147 frame.render_widget(self, frame.area()); 148 } 149 fn handle_events(&mut self, poll: bool) -> std::io::Result<()> { 150 let event_ready = event::poll(Duration::from_millis(15))?; 151 if (!poll || event_ready) 152 && let Event::Key(k) = event::read()? 153 { 154 self.handle_key_event(k) 155 } 156 Ok(()) 157 } 158 fn handle_key_event(&mut self, key_event: KeyEvent) { 159 match key_event.code { 160 KeyCode::Char('q') => { 161 self.exit = true; 162 } 163 KeyCode::Char('R') => self.perilus.reset(), 164 KeyCode::Char('n') => { 165 self.show_register_aliases = !self.show_register_aliases; 166 } 167 KeyCode::Char(' ') => self.perilus.pulse_clock(), 168 KeyCode::Char('F') => { 169 self.run_state = RunState::Step { 170 ignore_current_state: true, 171 }; 172 } 173 KeyCode::Char('T') => { 174 if let Mode::EditMemory = self.mode { 175 self.run_state = RunState::RunTo { 176 index: self.memory_cursor.get(), 177 }; 178 } 179 } 180 KeyCode::Char('G') => { 181 self.run_state = RunState::Run; 182 } 183 184 KeyCode::Esc => { 185 self.mode = Mode::Normal; 186 self.run_state = RunState::Idle; 187 } 188 KeyCode::Char('r') => { 189 if self.register_cursor == 0 { 190 self.mode = Mode::EditPc; 191 } else { 192 self.mode = Mode::EditRegister; 193 } 194 } 195 KeyCode::Char('m') => { 196 self.mode = Mode::EditMemory; 197 } 198 199 KeyCode::Char('k') | KeyCode::Up => match self.mode { 200 Mode::Normal | Mode::EditPc => (), 201 Mode::EditRegister => { 202 if self.register_cursor % 8 != 0 { 203 self.register_cursor = self.register_cursor.saturating_sub(1) 204 } 205 if self.register_cursor == 0 { 206 self.mode = Mode::EditPc; 207 } 208 } 209 Mode::EditMemory => { 210 if self.memory_cursor >= 8 { 211 self.memory_cursor = self.memory_cursor.saturating_sub(8); 212 } 213 } 214 }, 215 KeyCode::Char('j') | KeyCode::Down => match self.mode { 216 Mode::Normal => (), 217 Mode::EditPc => { 218 self.mode = Mode::EditRegister; 219 self.register_cursor = Register::const_new::<1>() 220 } 221 Mode::EditRegister => { 222 if self.register_cursor % 8 != 7 { 223 self.register_cursor = self.register_cursor.saturating_add(1); 224 } 225 } 226 Mode::EditMemory => { 227 if self.memory_cursor <= 1016 { 228 self.memory_cursor = self.memory_cursor.saturating_add(8); 229 } 230 } 231 }, 232 KeyCode::Char('h') | KeyCode::Left => match self.mode { 233 Mode::Normal | Mode::EditPc => (), 234 Mode::EditRegister => { 235 if self.register_cursor > 7 { 236 self.register_cursor = self.register_cursor.saturating_sub(8); 237 } 238 if self.register_cursor == 0 { 239 self.mode = Mode::EditPc; 240 } 241 } 242 Mode::EditMemory => { 243 if self.memory_cursor % 8 != 0 { 244 self.memory_cursor = self.memory_cursor.saturating_sub(1); 245 } 246 } 247 }, 248 KeyCode::Char('l') | KeyCode::Right => match self.mode { 249 Mode::Normal => (), 250 Mode::EditPc => { 251 self.mode = Mode::EditRegister; 252 self.register_cursor = Register::const_new::<8>(); 253 } 254 Mode::EditRegister => { 255 if self.register_cursor < 24 { 256 self.register_cursor = 257 (self.register_cursor + 8).min(Register::const_new::<31>()); 258 } 259 } 260 Mode::EditMemory => { 261 if self.memory_cursor % 8 != 7 { 262 self.memory_cursor = self.memory_cursor.saturating_add(1); 263 } 264 } 265 }, 266 KeyCode::Char(n) 267 if [ 268 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 269 ] 270 .contains(&n) => 271 { 272 let new_nibble = n.to_digit(16).unwrap(); 273 match self.mode { 274 Mode::Normal => (), 275 Mode::EditPc => { 276 let current_value = self.perilus.get_pc(); 277 let new_value = (current_value << 4) + new_nibble; 278 self.perilus.set_pc(new_value); 279 self.current_pc = self.perilus.get_pc(); 280 } 281 Mode::EditRegister => { 282 let current_value = 283 self.perilus.get_registers()[self.register_cursor.get() as usize]; 284 let new_value = (current_value << 4) + new_nibble; 285 self.perilus.set_register(self.register_cursor, new_value); 286 } 287 Mode::EditMemory => { 288 let current_value = 289 self.perilus.get_memory()[self.memory_cursor.get() as usize]; 290 let new_value = (current_value << 4) + new_nibble; 291 self.perilus.set_memory(self.memory_cursor.get(), new_value); 292 } 293 } 294 } 295 KeyCode::Char('C') => match self.mode { 296 Mode::Normal => (), 297 Mode::EditPc => self.perilus.set_pc(0), 298 Mode::EditRegister => self.perilus.set_register(self.register_cursor, 0), 299 Mode::EditMemory => self.perilus.set_memory(self.memory_cursor.get(), 0), 300 }, 301 KeyCode::Char('J') => { 302 if let Mode::EditMemory = self.mode { 303 self.perilus.set_pc(self.memory_cursor.get() * 4); 304 } 305 } 306 _ => (), 307 } 308 } 309 // TODO move this method to Register 310 fn register_alias(reg: Register) -> String { 311 match reg.get() { 312 0 => "zero".into(), 313 1 => "ra".into(), 314 2 => "sp".into(), 315 3 => "gp".into(), 316 4 => "tp".into(), 317 r if (5..=7).contains(&r) => format!("t{}", r - 5), 318 8 => "s0/fp".into(), 319 9 => "s1".into(), 320 r if (10..=17).contains(&r) => format!("a{}", r - 10), 321 r if (18..=27).contains(&r) => format!("s{}", r - 16), 322 r if (28..=31).contains(&r) => format!("t{}", r - 25), 323 r => unreachable!("got invalid register '{r}'"), 324 } 325 } 326 } 327 328 impl Widget for &App { 329 fn render(self, _area: Rect, buf: &mut Buffer) { 330 let run_state_area = Rect::new(72, 6, 27, 1); 331 Paragraph::new(format!("run state {}", self.run_state)).render(run_state_area, buf); 332 let control_unit_state_area = Rect::new(72, 7, 27, 1); 333 Paragraph::new(format!( 334 " cu state {:<2}", 335 self.perilus.get_control_unit_state() 336 )) 337 .render(control_unit_state_area, buf); 338 let pc_area = Rect::new(0, 0, 17, 1); 339 let mut pc_style = Style::default().fg(Color::Green); 340 if let Mode::EditPc = self.mode { 341 pc_style = pc_style.fg(Color::Black).bg(Color::Gray); 342 } 343 Paragraph::new(format!(" pc {:08x}", self.current_pc)) 344 .style(pc_style) 345 .render(pc_area, buf); 346 let register_file = self.perilus.get_registers(); 347 for (r, reg) in register_file.iter().enumerate() { 348 if r == 0 { 349 continue; 350 } 351 let reg_area = Rect::new(r as u16 / 8 * 18, r as u16 % 8, 17, 1); 352 let mut reg_style = Style::default(); 353 if let Mode::EditRegister = self.mode 354 && self.register_cursor.get() == r as u32 355 { 356 reg_style = reg_style.fg(Color::Black).bg(Color::Gray); 357 } 358 let reg_name = if self.show_register_aliases { 359 App::register_alias(Register::new(r as u32).unwrap()) 360 } else { 361 format!("x{r}") 362 }; 363 Paragraph::new(format!("{reg_name:>8} {reg:08x}")) 364 .style(reg_style) 365 .render(reg_area, buf); 366 } 367 let memory = self.perilus.get_memory(); 368 for offset in self.memory_top.get() as u16..self.memory_bottom.get() as u16 { 369 let row = (offset - self.memory_top.get() as u16) / 8 + 9; 370 if offset % 8 == 0 { 371 let label_area = Rect::new(0, row, 8, 1); 372 Paragraph::new(format!("{:08x}", offset * 4)).render(label_area, buf); 373 } 374 let mem_area = Rect::new(offset % 8 * 9 + 9, row, 8, 1); 375 let mut mem_style = Style::default(); 376 if self.current_pc == offset as u32 * 4 { 377 mem_style = mem_style.fg(Color::Green); 378 } 379 if let Mode::EditMemory = self.mode 380 && self.memory_cursor.get() == offset as u32 381 { 382 if self.memory_cursor.get() * 4 == self.current_pc { 383 mem_style = mem_style.fg(Color::White).bg(Color::Green); 384 } else { 385 mem_style = mem_style.fg(Color::Black).bg(Color::White); 386 } 387 } 388 Paragraph::new(format!("{:08x}", memory[offset as usize])) 389 .style(mem_style) 390 .render(mem_area, buf); 391 let ascii_area = Rect::new(82 + (offset % 8) * 4, row, 4, 1); 392 let ascii: String = memory[offset as usize] 393 .to_le_bytes() 394 .map(|b| { 395 if (b' '..=b'~').contains(&b) { 396 char::from_u32(b as u32).unwrap_or('.') 397 } else { 398 '.' 399 } 400 }) 401 .iter() 402 .collect(); 403 Paragraph::new(ascii) 404 .style(mem_style) 405 .render(ascii_area, buf); 406 } 407 } 408 } 409 410 fn run_interactive(mut app: App) -> color_eyre::Result<()> { 411 color_eyre::install()?; 412 ratatui::run(|terminal| app.run(terminal))?; 413 Ok(()) 414 } 415 416 const EBREAK_INSTRUCTION: u32 = 0x00100073; 417 const TEST_MAX_CYCLES: u32 = 1_000_000; 418 const TEST_PASS_VALUE: u32 = 0xebbb3689; 419 const TEST_FAIL_VALUE: u32 = 0xd02a545a; 420 421 fn main() -> Result<(), Box<dyn Error>> { 422 let cli = Cli::parse(); 423 let perilus = Perilus::init(); 424 425 match cli.command { 426 Some(Command::Interactive { file }) => { 427 if let Some(file) = file { 428 perilus.load_file(file)?; 429 } 430 let app = App::new(perilus); 431 run_interactive(app).map_err(|e| -> Box<dyn Error> { e.to_string().into() })?; 432 Ok(()) 433 } 434 Some(Command::Test { file }) => { 435 perilus.load_file(file)?; 436 let mut cycles = 0; 437 loop { 438 perilus.pulse_clock(); 439 cycles += 1; 440 if cycles > TEST_MAX_CYCLES { 441 return Err("Test execution hit maximum cycle count limit".into()); 442 } 443 if perilus.get_control_unit_state() == 0 { 444 let pc = perilus.get_pc(); 445 let instr = perilus.get_memory().get((pc / 4) as usize); 446 if instr.is_none() { 447 return Err(format!( 448 "Program counter diverged to an address outside memory: 0x{pc:08x}" 449 ) 450 .into()); 451 } 452 if let Some(&EBREAK_INSTRUCTION) = instr { 453 let result = perilus.get_registers()[1]; 454 match result { 455 TEST_PASS_VALUE => return Ok(()), 456 TEST_FAIL_VALUE => return Err("Test failed".into()), 457 v => return Err(format!("Got unexpected test result 0x{v:08x}").into()), 458 } 459 } 460 } 461 } 462 } 463 None => { 464 let app = App::new(perilus); 465 run_interactive(app).map_err(|e| -> Box<dyn Error> { e.to_string().into() })?; 466 Ok(()) 467 } 468 } 469 }