implement call & return instructions

This commit is contained in:
Denis-Cosmin Nutiu 2024-12-06 15:10:03 +02:00
parent 49e078683d
commit 52c03aa6ae
4 changed files with 69 additions and 39 deletions

View file

@ -1,8 +1,8 @@
use ratatui::DefaultTerminal; use ratatui::style::Color;
use ratatui::style::{Color, Style};
use ratatui::symbols::Marker; use ratatui::symbols::Marker;
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::widgets::canvas::Canvas; use ratatui::widgets::canvas::Canvas;
use ratatui::widgets::{Block, Borders};
use ratatui::DefaultTerminal;
/// Represents the display's width in pixels. /// Represents the display's width in pixels.
const DISPLAY_WIDTH: usize = 64; const DISPLAY_WIDTH: usize = 64;
@ -19,8 +19,7 @@ pub trait Display {
} }
/// Simple terminal display for the Chip8's emulator. /// Simple terminal display for the Chip8's emulator.
pub struct TerminalDisplay { pub struct TerminalDisplay {}
}
impl TerminalDisplay { impl TerminalDisplay {
pub fn new() -> TerminalDisplay { pub fn new() -> TerminalDisplay {
@ -52,13 +51,13 @@ impl Display for TerminalDisplay {
/// Ratatui based TUI display. /// Ratatui based TUI display.
pub struct RatatuiDisplay { pub struct RatatuiDisplay {
terminal: DefaultTerminal terminal: DefaultTerminal,
} }
impl RatatuiDisplay { impl RatatuiDisplay {
pub fn new() -> RatatuiDisplay { pub fn new() -> RatatuiDisplay {
RatatuiDisplay { RatatuiDisplay {
terminal: ratatui::init() terminal: ratatui::init(),
} }
} }
} }
@ -69,9 +68,14 @@ impl Display for RatatuiDisplay {
} }
fn render(&mut self, display_data: &[bool; DISPLAY_WIDTH * DISPLAY_HEIGHT]) { fn render(&mut self, display_data: &[bool; DISPLAY_WIDTH * DISPLAY_HEIGHT]) {
self.terminal.draw(|frame| { self.terminal
.draw(|frame| {
let canvas = Canvas::default() let canvas = Canvas::default()
.block(Block::default().title("Chip8 Emulator by nuculabs.dev").borders(Borders::ALL)) .block(
Block::default()
.title("Chip8 Emulator by nuculabs.dev")
.borders(Borders::ALL),
)
.marker(Marker::Bar) .marker(Marker::Bar)
.paint(|ctx| { .paint(|ctx| {
for row in 0..DISPLAY_HEIGHT { for row in 0..DISPLAY_HEIGHT {
@ -92,9 +96,8 @@ impl Display for RatatuiDisplay {
.y_bounds([0.0, DISPLAY_HEIGHT as f64]); .y_bounds([0.0, DISPLAY_HEIGHT as f64]);
// Render the canvas widget // Render the canvas widget
// Center the paragraph in the terminal
frame.render_widget(canvas, frame.area()); frame.render_widget(canvas, frame.area());
}).expect("failed to draw"); })
.expect("failed to draw");
} }
} }

View file

@ -149,9 +149,11 @@ where
ProcessorInstruction::SetIndexRegister(data) => { ProcessorInstruction::SetIndexRegister(data) => {
trace!("Set index register to data {:04x}", data); trace!("Set index register to data {:04x}", data);
self.index_register = data; self.index_register = data;
}, }
ProcessorInstruction::Draw(vx_register, vy_register, num_rows) => { 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 x_coordinate = self.registers[vx_register as usize];
let y_coordinate = self.registers[vy_register as usize]; let y_coordinate = self.registers[vy_register as usize];
@ -188,6 +190,18 @@ where
self.display.render(&self.display_data); 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 => { ProcessorInstruction::UnknownInstruction => {
warn!("Unknown instruction: {:04x}, skipping.", instruction); warn!("Unknown instruction: {:04x}, skipping.", instruction);
} }

View file

@ -26,6 +26,10 @@ pub enum ProcessorInstruction {
SetIndexRegister(u16), SetIndexRegister(u16),
/// Draws to the screen. /// Draws to the screen.
Draw(u8, u8, u8), 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 /// Unknown instruction
UnknownInstruction, UnknownInstruction,
} }
@ -86,9 +90,7 @@ impl Instruction {
) )
} }
// Set index register // Set index register
(0xA, _, _, _) => { (0xA, _, _, _) => ProcessorInstruction::SetIndexRegister(Self::grab_inner_data(data)),
ProcessorInstruction::SetIndexRegister(Self::grab_inner_data(data))
},
// Draw on screen // Draw on screen
(0xD, _, _, _) => { (0xD, _, _, _) => {
// DXYN // DXYN
@ -98,6 +100,18 @@ impl Instruction {
Self::grab_last_nibble(data), 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 // Unknown instruction
_ => ProcessorInstruction::UnknownInstruction, _ => ProcessorInstruction::UnknownInstruction,
} }
@ -118,7 +132,6 @@ impl Instruction {
((data & 0xF000) >> 12) as u8 ((data & 0xF000) >> 12) as u8
} }
/// Grabs the first nibble from the data. /// Grabs the first nibble from the data.
fn grab_first_nibble(data: u16) -> u8 { fn grab_first_nibble(data: u16) -> u8 {
((data & 0x0F00) >> 8) as u8 ((data & 0x0F00) >> 8) as u8

View file

@ -1,4 +1,4 @@
use crate::display::{RatatuiDisplay}; use crate::display::RatatuiDisplay;
use crate::emulator::Emulator; use crate::emulator::Emulator;
use env_logger; use env_logger;