perilus

git clone https://git.bradleygannon.com/perilus.git
Log | Files | Refs | Submodules | README

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 }