implement call & return instructions
This commit is contained in:
parent
49e078683d
commit
52c03aa6ae
4 changed files with 69 additions and 39 deletions
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue