From 52c03aa6aef3de18d8dbbf34ebe314e5be90b4b1 Mon Sep 17 00:00:00 2001 From: Denis Nutiu Date: Fri, 6 Dec 2024 15:10:03 +0200 Subject: [PATCH] implement call & return instructions --- src/display.rs | 67 ++++++++++++++++++++++++---------------------- src/emulator.rs | 18 +++++++++++-- src/instruction.rs | 21 ++++++++++++--- src/main.rs | 2 +- 4 files changed, 69 insertions(+), 39 deletions(-) diff --git a/src/display.rs b/src/display.rs index 5427e1e..b5bcfa5 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,8 +1,8 @@ -use ratatui::DefaultTerminal; -use ratatui::style::{Color, Style}; +use ratatui::style::Color; use ratatui::symbols::Marker; -use ratatui::widgets::{Block, Borders, Paragraph}; use ratatui::widgets::canvas::Canvas; +use ratatui::widgets::{Block, Borders}; +use ratatui::DefaultTerminal; /// Represents the display's width in pixels. const DISPLAY_WIDTH: usize = 64; @@ -19,8 +19,7 @@ pub trait Display { } /// Simple terminal display for the Chip8's emulator. -pub struct TerminalDisplay { -} +pub struct TerminalDisplay {} impl TerminalDisplay { pub fn new() -> TerminalDisplay { @@ -52,13 +51,13 @@ impl Display for TerminalDisplay { /// Ratatui based TUI display. pub struct RatatuiDisplay { - terminal: DefaultTerminal + terminal: DefaultTerminal, } impl RatatuiDisplay { pub fn new() -> RatatuiDisplay { RatatuiDisplay { - terminal: ratatui::init() + terminal: ratatui::init(), } } } @@ -69,32 +68,36 @@ impl Display for RatatuiDisplay { } fn render(&mut self, display_data: &[bool; DISPLAY_WIDTH * DISPLAY_HEIGHT]) { - self.terminal.draw(|frame| { - let canvas = Canvas::default() - .block(Block::default().title("Chip8 Emulator by nuculabs.dev").borders(Borders::ALL)) - .marker(Marker::Bar) - .paint(|ctx| { - for row in 0..DISPLAY_HEIGHT { - for column in 0..DISPLAY_WIDTH { - if display_data[row * DISPLAY_WIDTH + column] { - ctx.draw(&ratatui::widgets::canvas::Rectangle { - x: column as f64, - y: DISPLAY_HEIGHT as f64 - row as f64, - width: 1.0, - height: 1.0, - color: Color::White, - }); + self.terminal + .draw(|frame| { + let canvas = Canvas::default() + .block( + Block::default() + .title("Chip8 Emulator by nuculabs.dev") + .borders(Borders::ALL), + ) + .marker(Marker::Bar) + .paint(|ctx| { + for row in 0..DISPLAY_HEIGHT { + for column in 0..DISPLAY_WIDTH { + if display_data[row * DISPLAY_WIDTH + column] { + ctx.draw(&ratatui::widgets::canvas::Rectangle { + x: column as f64, + y: DISPLAY_HEIGHT as f64 - row as f64, + width: 1.0, + height: 1.0, + color: Color::White, + }); + } } } - } - }) - .x_bounds([0.0, DISPLAY_WIDTH as f64]) - .y_bounds([0.0, DISPLAY_HEIGHT as f64]); + }) + .x_bounds([0.0, DISPLAY_WIDTH as f64]) + .y_bounds([0.0, DISPLAY_HEIGHT as f64]); - // Render the canvas widget - - // Center the paragraph in the terminal - frame.render_widget(canvas, frame.area()); - }).expect("failed to draw"); + // Render the canvas widget + frame.render_widget(canvas, frame.area()); + }) + .expect("failed to draw"); } -} \ No newline at end of file +} diff --git a/src/emulator.rs b/src/emulator.rs index 25b721f..64d1b22 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -149,9 +149,11 @@ where ProcessorInstruction::SetIndexRegister(data) => { trace!("Set index register to data {:04x}", data); self.index_register = data; - }, + } ProcessorInstruction::Draw(vx_register, vy_register, num_rows) => { - trace!("Draw vx_register={vx_register} vy_register={vy_register} pixels={num_rows}"); + trace!( + "Draw vx_register={vx_register} vy_register={vy_register} pixels={num_rows}" + ); let x_coordinate = self.registers[vx_register as usize]; let y_coordinate = self.registers[vy_register as usize]; @@ -188,6 +190,18 @@ where self.display.render(&self.display_data); } + ProcessorInstruction::Return => { + let value = self.stack.pop().unwrap(); + trace!("Return to {value:04x}"); + self.program_counter = value; + } + ProcessorInstruction::Call(address) => { + trace!("Call {address:04x}"); + // Save PC to the stack + self.stack.push(self.program_counter); + // Set PC to subroutine address + self.program_counter = address; + } ProcessorInstruction::UnknownInstruction => { warn!("Unknown instruction: {:04x}, skipping.", instruction); } diff --git a/src/instruction.rs b/src/instruction.rs index 3116267..20bf7de 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -26,6 +26,10 @@ pub enum ProcessorInstruction { SetIndexRegister(u16), /// Draws to the screen. Draw(u8, u8, u8), + /// Call sets PC to the address and saves the return address on the stack + Call(u16), + /// Pops the stack and sets the PC + Return, /// Unknown instruction UnknownInstruction, } @@ -86,9 +90,7 @@ impl Instruction { ) } // Set index register - (0xA, _, _, _) => { - ProcessorInstruction::SetIndexRegister(Self::grab_inner_data(data)) - }, + (0xA, _, _, _) => ProcessorInstruction::SetIndexRegister(Self::grab_inner_data(data)), // Draw on screen (0xD, _, _, _) => { // DXYN @@ -98,6 +100,18 @@ impl Instruction { Self::grab_last_nibble(data), ) } + /* + 00EE and 2NNN: + + 2NNN calls the subroutine at memory location NNN. In other words, just like 1NNN, + you should set PC to NNN. However, the difference between a jump and a call is that + this instruction should first push the current PC to the stack, so the subroutine can return later. + + Returning from a subroutine is done with 00EE, and it does this by removing + (“popping”) the last address from the stack and setting the PC to it. + */ + (0x0, 0x0, 0xE, 0xE) => ProcessorInstruction::Return, + (0x2, _, _, _) => ProcessorInstruction::Call(Self::grab_inner_data(data)), // Unknown instruction _ => ProcessorInstruction::UnknownInstruction, } @@ -118,7 +132,6 @@ impl Instruction { ((data & 0xF000) >> 12) as u8 } - /// Grabs the first nibble from the data. fn grab_first_nibble(data: u16) -> u8 { ((data & 0x0F00) >> 8) as u8 diff --git a/src/main.rs b/src/main.rs index b3d625a..a44df51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use crate::display::{RatatuiDisplay}; +use crate::display::RatatuiDisplay; use crate::emulator::Emulator; use env_logger;