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, 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,9 +68,14 @@ impl Display for RatatuiDisplay {
|
|||
}
|
||||
|
||||
fn render(&mut self, display_data: &[bool; DISPLAY_WIDTH * DISPLAY_HEIGHT]) {
|
||||
self.terminal.draw(|frame| {
|
||||
self.terminal
|
||||
.draw(|frame| {
|
||||
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)
|
||||
.paint(|ctx| {
|
||||
for row in 0..DISPLAY_HEIGHT {
|
||||
|
@ -92,9 +96,8 @@ impl Display for RatatuiDisplay {
|
|||
.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");
|
||||
})
|
||||
.expect("failed to draw");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::display::{RatatuiDisplay};
|
||||
use crate::display::RatatuiDisplay;
|
||||
use crate::emulator::Emulator;
|
||||
use env_logger;
|
||||
|
||||
|
|
Loading…
Reference in a new issue