emma now has ability to decode an instruction and to display video and system memory

This commit is contained in:
2024-08-17 13:40:19 -04:00
parent ceb62d2c53
commit 6dc9ce3721
21 changed files with 2772 additions and 374 deletions
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "emmaemu"
version = "0.1.0"
edition = "2021"
autobenches = true
[dependencies]
ratatui = "0.28.0"
+516
View File
@@ -0,0 +1,516 @@
use std::io::{stdout, Result};
use font::load_fonts_into_memory;
use ratatui::{
backend::CrosstermBackend,
crossterm::{
event::{self, KeyCode, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
},
layout::{Alignment, Rect},
style::Stylize,
widgets::{List, Paragraph},
Frame, Terminal,
};
// nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
pub fn read_addr_from_instruction(instruction_to_read_from: u16) -> u16 {
instruction_to_read_from & 0b0000111111111111
}
// n or nibble - A 4-bit value, the lowest 4 bits of the instruction
pub fn read_nibble_from_instruction(instruction_to_read_from: u16) -> u16 {
instruction_to_read_from & 0b0000000000001111
}
// x - A 4-bit value, the lower 4 bits of the high byte of the instruction
pub fn read_x_from_instruction(instruction_to_read_from: u16) -> u16 {
(instruction_to_read_from & 0b0000111100000000).rotate_right(8)
}
// y - A 4-bit value, the upper 4 bits of the low byte of the instruction
pub fn read_y_from_instruction(instruction_to_read_from: u16) -> u16 {
(instruction_to_read_from & 0b0000000011110000).rotate_right(4)
}
// kk or byte - An 8-bit value, the lowest 8 bits of the instruction
pub fn read_byte_from_instruction(instruction_to_read_from: u16) -> u16 {
(instruction_to_read_from & 0b0000000011111111)
}
mod font;
#[derive(Clone)]
struct Chip8CpuData {
pub pc: u16,
pub sp: u8,
pub memory: [u8; CHIP8_MEMORY_SIZE as usize],
pub video_memory: [bool; CHIP8_VIDEO_MEMORY],
pub registers: [u8; 16],
pub sound_timer: u8,
pub delay_timer: u8,
pub i_register: u16,
}
const CHIP8_REGISTER_COUNT: i32 = 16;
const CHIP8_MEMORY_SIZE: i32 = 2048i32;
const CHIP8_VIDEO_WIDTH: i32 = 64i32;
const CHIP8_VIDEO_HEIGHT: i32 = 32i32;
const CHIP8_VIDEO_MEMORY: usize = (CHIP8_VIDEO_HEIGHT * CHIP8_VIDEO_WIDTH) as usize;
const CHIP8_ROM_SIZE: usize = 512;
impl Default for Chip8CpuData {
fn default() -> Self {
let mut memory: [u8; 2048] = [0; 2048];
memory = load_fonts_into_memory(memory);
dump_memory_to_console(memory);
Self {
pc: 0x0200,
sp: 0x00,
memory: [0; 2048],
video_memory: [false; CHIP8_VIDEO_MEMORY],
registers: [0; CHIP8_REGISTER_COUNT as usize],
sound_timer: 0xFF,
delay_timer: 0xFF,
i_register: 0x0000,
}
}
}
impl Chip8CpuData {
pub fn new() -> Self {
Chip8CpuData::default()
}
}
fn system_memory_to_text_render(data_to_dump: [u8; 2048]) -> String {
let mut to_return = String::new();
for i in 0..256 {
to_return += &format!("{:x}\t", data_to_dump[i as usize]).to_string();
if ((i + 1) % CHIP8_REGISTER_COUNT) == 0 {
to_return += "\n";
}
}
to_return
}
fn dump_memory_to_console(data_to_dump: [u8; 2048]) {
println!("STARTING TO DUMP MEMORY TO CONSOLE");
println!("{}", system_memory_to_text_render(data_to_dump));
println!("DONE DUMPING!");
panic!("DONE DUMPING");
}
fn display_data_to_text_render(data_display: [bool; 2048]) -> String {
let mut output = String::new();
for row in 0..32 {
let row_offset = row * 32;
for column in 0..64 {
let data_position = row_offset + column;
println!("DP {} {} {} {}", data_position, row, row_offset, column);
output += if data_display[data_position] {
"*"
} else {
" "
};
}
output += "\n";
}
output
}
fn render_display(display_data: [bool; 2048]) {
println!("{}", display_data_to_text_render(display_data));
}
#[derive(Clone)]
enum Chip8CpuStates {
WaitingForInstruction,
ExecutingInstruction,
Error,
}
struct Chip8CpuState {
state: Chip8CpuStates,
cpu: Chip8CpuData
}
enum Chip8CpuInstructions {
SysAddr(u16), // 0x0nnn Exit to System Call
CLS, // 0x00E0 Clear Screen
RET, // 0x00EE Return from Subroutine
JpAddr(u16), // 0x1nnn Jump to Address
CallAddr(u16), // 0x2nnn Call Subroutine
SeVxByte(u16, u16), // 0x3xkk Skip next instruction if Vx = kk.
SneVxByte(u16, u16), // 0x4xkk Skip next instruction if Vx != kk
SeVxVy(u16, u16), // 0x5xy0 Skip next instruction if Vx == Vy
LdVxByte(u16, u16), // 0x6xkk Set Vx = kk
AddVxByte(u16, u16), // 0x7xkk Set Vx = Vx + kk
LdVxVy(u16, u16), // 0x8xy0 Set value of Vy in Vx
OrVxVy(u16, u16), // 0x8xy1 Set Vx = Vx OR Vy
AndVxVy(u16, u16), // 0x8xy2 Set Vx = Vx AND Vy
XorVxVy(u16, u16), // 0x8xy3 Set Vx = Vx XOR Vy
AddVxVy(u16, u16), // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
SubVxVy(u16, u16), // 0x8xy5 Set Vx = Vx - Vy (Set VF NOT Borrow)
ShrVxVy(u16, u16), // 0x8xy6 Set Vx = Vx SHR 1 (Shift Rotated Right 1)
SubnVxVy(u16, u16), // 0x8xy7 Set Vx = Vy - Vx (Set VF NOT Borrow)
ShlVxVy(u16, u16), // 0x8xyE Shift Left
SneVxVy(u16, u16), // 0x9xy0 Skip next instruction if Vx != Vy
LdIAddr(u16), // 0xAnnn VI = nnn
JpV0Addr(u16), // 0xBnnn Jump to nnn+V0
RndVxByte(u16, u16), // 0xCxkk Vx = random byte AND kk
DrawVxVyNibble(u16, u16, u16), // 0xDxyn Display N byte sprite starting at Vx to Vy
SkpVx(u16), // 0xE09E Skip next instruction if key in Vx pressed
SnkpVx(u16), // 0xE0A1 Skip next instruction if key in Vx NOT pressed
LdVxDt(u16), // 0xFx07 Set Vx = Delay timer
LdVxK(u16), // 0xFx0A Wait for key, put in Vx
LdDtVx(u16), // 0xFx15 Set Delay Timer
LdStVx(u16), // 0xFx18 Set Sount Timer
AddIVx(u16), // 0xFx1E I = I + Vx
LdFVu(u16), // 0xFx29 Set I = Location of sprite for Digit Vx
LdBVx(u16), // 0xFx33 Store BCD of Vx in I, I+1, I+2
LdIVx(u16), // 0xFx55 Store V0 to Vx in memory starting at I
LdVxI(u16), // 0xFx65 Load V0 to Vx in memory starting at I
XXXXERRORINSTRUCTION,
}
fn bytes_to_instruction(to_read: u16) -> Chip8CpuInstructions {
let mut decoded_instruction = Chip8CpuInstructions::XXXXERRORINSTRUCTION;
match to_read {
0x00E0 => {
// 00E0 - CLS
// Clear the display.
decoded_instruction = Chip8CpuInstructions::CLS;
}
0x00EE => {
// 00EE - RET
// Return from a subroutine.
decoded_instruction = Chip8CpuInstructions::RET;
}
0x0000..=0x0FFF => {
// 0nnn - SYS addr
// Jump to a machine code routine at nnn.
decoded_instruction =
Chip8CpuInstructions::SysAddr(read_addr_from_instruction(to_read));
}
0x1000..=0x1FFF => {
// 1nnn - JP addr
// Jump to location nnn.
decoded_instruction = Chip8CpuInstructions::JpAddr(read_addr_from_instruction(to_read));
}
0x2000..=0x2FFF => {
// 2nnn - CALL addr
// Call subroutine at nnn.
decoded_instruction =
Chip8CpuInstructions::CallAddr(read_addr_from_instruction(to_read));
}
0x3000..=0x3FFF => {
// 3xkk - SE Vx, byte
// Skip next instruction if Vx = kk.
decoded_instruction = Chip8CpuInstructions::SeVxByte(
read_x_from_instruction(to_read),
read_byte_from_instruction(to_read),
)
}
0x4000..=0x4FFF => {
// 4xkk - SNE Vx, byte
// Skip next instruction if Vx != kk.
decoded_instruction = Chip8CpuInstructions::SneVxByte(
read_x_from_instruction(to_read),
read_byte_from_instruction(to_read),
);
}
0x5000..=0x5FF0 => {
// 5xy0 - SE Vx, Vy
// Skip next instruction if Vx = Vy.
decoded_instruction = Chip8CpuInstructions::SeVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
)
}
0x6000..=0x6FFF => {
// 6xkk - LD Vx, byte
// Set Vx = kk.
decoded_instruction = Chip8CpuInstructions::LdVxVy(
read_x_from_instruction(to_read),
read_byte_from_instruction(to_read),
);
}
0x7000..=0x7FFF => {
// ADD Vx, Byte
decoded_instruction = Chip8CpuInstructions::AddVxByte(
read_x_from_instruction(to_read),
read_byte_from_instruction(to_read),
);
}
0x8000..=0x8FFE => {
// 0x8000 Series
let last_nibble = to_read | 0x8000;
match last_nibble {
0x0 => {
// LD Vx, Vy
decoded_instruction = Chip8CpuInstructions::LdVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0x1 => {
// OR Vx, Vy
decoded_instruction = Chip8CpuInstructions::OrVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0x2 => {
// AND Vx, Vy
decoded_instruction = Chip8CpuInstructions::AndVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0x3 => {
// XOR Vx, Vy
decoded_instruction = Chip8CpuInstructions::XorVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0x4 => {
// AND Vx, Vy
decoded_instruction = Chip8CpuInstructions::AndVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0x5 => {
// SUB Vx, Vy
decoded_instruction = Chip8CpuInstructions::SubnVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0x6 => {
// SHR Vx, {, Vy }
decoded_instruction = Chip8CpuInstructions::ShrVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0x7 => {
// SUBN Vx, Vy
decoded_instruction = Chip8CpuInstructions::SubnVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0xE => {
// SHL Vx, {, Vy}
decoded_instruction = Chip8CpuInstructions::ShlVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
_ => {
panic!("UNABLE TO DECODE 0x8000 SERIES INSTRUCTION");
}
}
}
0x9000..=0x9FF0 => {
// SNE Vx, Vy
decoded_instruction = Chip8CpuInstructions::SneVxVy(
read_x_from_instruction(to_read),
read_y_from_instruction(to_read),
);
}
0xA000..=0xAFFF => {
// LD I, Addr
decoded_instruction =
Chip8CpuInstructions::LdIAddr(read_addr_from_instruction(to_read));
}
0xB000..=0xBFFF => {
// JP V0, Addr
}
0xC000..=0xCFFF => {
// RND Vx, byte
}
0xD000..0xDFFF => {
// DRAW Vx, Vy, nibble
}
0xE09E..=0xEFA1 => {}
_ => {
panic!("UNABLE TO DECODE INSTRUCTION")
}
}
return decoded_instruction;
}
impl Chip8CpuState {
fn execute(&mut self, event: Chip8CpuInstructions) {
match (self.state.clone(), event) {
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SysAddr(new_address)) => {
self.cpu.pc = new_address;
},
_ => ()
}
}
}
#[derive(Clone)]
struct Chip8System {
pub system_memory: [u8; 2048],
pub rom: [u8; 512],
pub sound_timer: u8,
pub system_timer: u8,
}
impl Default for Chip8System {
fn default() -> Self {
// init by loading the fonts...
let mut working_system_memory: [u8; CHIP8_MEMORY_SIZE as usize] =
[0; CHIP8_MEMORY_SIZE as usize];
// Load fonts to memory
working_system_memory = load_fonts_into_memory(working_system_memory);
Self {
system_memory: working_system_memory,
rom: [0; CHIP8_ROM_SIZE],
sound_timer: 0,
system_timer: 0,
}
}
}
impl Chip8System {
fn add_fonts_to_memory(
to_add_fonts_to: [u8; CHIP8_MEMORY_SIZE as usize],
) -> [u8; CHIP8_MEMORY_SIZE as usize] {
panic!("DONT USE THIS USE \"load_fonts_into_memory\" INSTEAD");
}
pub fn new() -> Self {
Chip8System {
system_memory: [0; CHIP8_MEMORY_SIZE as usize],
rom: [0; CHIP8_ROM_SIZE as usize],
sound_timer: 0,
system_timer: 0,
}
}
}
#[derive(Clone)]
struct AppState {
menu_items: Vec<String>,
selected_menu_item: i32,
system: Chip8CpuData,
}
impl Default for AppState {
fn default() -> Self {
println!("Creating new AppState");
Self {
menu_items: vec![
"Step CPU <F5>".into(),
"Reset CPU <F12>".into(),
"Item 3".into(),
],
selected_menu_item: 0,
system: Chip8CpuData {
..Default::default()
},
}
}
}
fn main() -> Result<()> {
// stdout().execute(EnterAlternateScreen)?;
// enable_raw_mode()?;
// let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
// terminal.clear()?;
let app = AppState::default();
loop {
// Draw Ui...
// terminal.draw(|frame| {
// render_menu_list(app.clone(), frame);
// render_cpu_state(app.system.clone(), frame);
// })?;
// ...handle Events.
if event::poll(std::time::Duration::from_millis(16))? {
if let event::Event::Key(key) = event::read()? {
match key.kind {
KeyEventKind::Press => match key.code {
KeyCode::F(5) => {
println!("Execute Next Instruction");
}
KeyCode::F(12) => {
println!("Resetting CPU");
}
_ => (),
},
_ => (),
}
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
break;
}
}
}
}
// stdout().execute(LeaveAlternateScreen)?;
// disable_raw_mode()?;
Ok(())
}
#[cfg(test)]
mod test {
use crate::*;
#[test]
fn blank_screen_renders_to_text() {
let blank_data: [bool; CHIP8_VIDEO_MEMORY] = [false; CHIP8_VIDEO_MEMORY];
let blank_screen = (" ".repeat(CHIP8_VIDEO_WIDTH.try_into().unwrap()) + "\n")
.repeat(CHIP8_VIDEO_HEIGHT.try_into().unwrap());
assert_eq!(display_data_to_text_render(blank_data), blank_screen);
}
#[test]
fn filled_screen_renders_to_text() {
let filled_data: [bool; CHIP8_VIDEO_MEMORY] = [true; CHIP8_VIDEO_MEMORY];
let filled_screen = ("*".repeat(CHIP8_VIDEO_WIDTH.try_into().unwrap()) + "\n")
.repeat(CHIP8_VIDEO_HEIGHT.try_into().unwrap());
assert_eq!(display_data_to_text_render(filled_data), filled_screen);
}
#[test]
fn grid_pattern_renders_to_text() {
let mut grid_data: [bool; CHIP8_VIDEO_MEMORY] = [false; CHIP8_VIDEO_MEMORY];
let mut expected_data = String::new();
for i in 0..CHIP8_VIDEO_MEMORY {
grid_data[i] = (i % 2 == 0);
if i % 2 == 0 {
grid_data[i] = true;
expected_data += "*";
} else {
grid_data[i] = false;
expected_data += " ";
}
if (i as i32 % CHIP8_VIDEO_WIDTH as i32 == CHIP8_VIDEO_WIDTH - 1) {
expected_data += "\n";
}
}
assert_eq!(display_data_to_text_render(grid_data), expected_data);
}
}
+55
View File
@@ -0,0 +1,55 @@
use crate::CHIP8_MEMORY_SIZE;
pub mod text_rendering;
pub const CHIP8FONT_0: [u8; 5] = [0xF0, 0x90, 0x90, 0x90, 0xF0];
pub const CHIP8FONT_1: [u8; 5] = [0x20, 0x60, 0x20, 0x20, 0x70];
pub const CHIP8FONT_2: [u8; 5] = [0xF0, 0x10, 0xF0, 0x80, 0xF0];
pub const CHIP8FONT_3: [u8; 5] = [0xF0, 0x10, 0xF0, 0x10, 0xF0];
pub const CHIP8FONT_4: [u8; 5] = [0x90, 0x90, 0xF0, 0x10, 0x10];
pub const CHIP8FONT_5: [u8; 5] = [0xF0, 0x80, 0xF0, 0x10, 0xF0];
pub const CHIP8FONT_6: [u8; 5] = [0xF0, 0x80, 0xF0, 0x90, 0xF0];
pub const CHIP8FONT_7: [u8; 5] = [0xF0, 0x10, 0x20, 0x40, 0x40];
pub const CHIP8FONT_8: [u8; 5] = [0xF0, 0x90, 0xF0, 0x90, 0xF0];
pub const CHIP8FONT_9: [u8; 5] = [0xF0, 0x90, 0xF0, 0x10, 0xF0];
pub const CHIP8FONT_A: [u8; 5] = [0xF0, 0x90, 0xF0, 0x90, 0x90];
pub const CHIP8FONT_B: [u8; 5] = [0xE0, 0x90, 0xE0, 0x90, 0xE0];
pub const CHIP8FONT_C: [u8; 5] = [0xF0, 0x80, 0x80, 0x80, 0xF0];
pub const CHIP8FONT_D: [u8; 5] = [0xE0, 0x90, 0x90, 0x90, 0xE0];
pub const CHIP8FONT_E: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0xF0];
pub const CHIP8FONT_F: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0x80];
pub fn load_fonts_into_memory(to_add_fonts_to: [u8; CHIP8_MEMORY_SIZE as usize])
-> [u8; CHIP8_MEMORY_SIZE as usize] {
let mut memory = to_add_fonts_to.clone();
let all_font_characters = [
CHIP8FONT_0,
CHIP8FONT_1,
CHIP8FONT_2,
CHIP8FONT_3,
CHIP8FONT_4,
CHIP8FONT_5,
CHIP8FONT_6,
CHIP8FONT_7,
CHIP8FONT_8,
CHIP8FONT_9,
CHIP8FONT_A,
CHIP8FONT_B,
CHIP8FONT_C,
CHIP8FONT_D,
CHIP8FONT_E,
CHIP8FONT_F,
];
for (font_index, current_font) in all_font_characters.iter().enumerate() {
for font_mem_offset in 0..=4 {
let real_offset = font_index * 5 + font_mem_offset;
memory[real_offset] = current_font[font_mem_offset];
}
}
memory
}
+36
View File
@@ -0,0 +1,36 @@
use ratatui::{style::Stylize, widgets::{List, Paragraph}, Frame};
use crate::{AppState, Chip8CpuData, CHIP8_REGISTER_COUNT};
pub fn render_cpu_state(cpu_state: Chip8CpuData, frame: &mut Frame) {
let mut area = frame.area();
let mut current_state: Vec<String> = vec![];
current_state.push(format!("Delay Timer {:X}", cpu_state.delay_timer));
current_state.push(format!("Sound Timer {:X}", cpu_state.sound_timer));
current_state.push(format!("PC {:X}", cpu_state.pc));
for i in 0 .. CHIP8_REGISTER_COUNT {
current_state.push(format!("V{}: {}", i, cpu_state.registers[i as usize]));
}
frame.render_widget(
List::new(current_state).blue().on_white(), area)
}
pub fn render_menu_list(state: AppState, frame: &mut Frame) {
let mut area = frame.area();
frame.render_widget(List::new(state.menu_items).blue().on_white(), area);
}
pub fn render_hello_world(frame: &mut Frame) {
let area = frame.area();
frame.render_widget(
Paragraph::new("Hello Ratatui! (press 'q' to quit)")
.white()
.on_blue(),
area,
);
}