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, 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");
}
}
}

View file

@ -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);
}

View file

@ -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

View file

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