8xy4 and 8xy5 pass.

more flag tests passing

removes legacy code
moves 'gemmaemu' into 'gemma' crate
moves 'gemmaimgui' into its own crate

update to gemma
This commit is contained in:
2024-10-10 10:19:34 -04:00
parent c7c3c6aa04
commit e176ee5638
35 changed files with 191 additions and 1552 deletions
-168
View File
@@ -1,168 +0,0 @@
use std::io::{stdout, Result};
/*
use emmaemu::{chip8::{computer::Chip8Computer, video::Chip8Video}, constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT, CHIP8_ROM_SIZE, CHIP8_VIDEO_MEMORY}};
use ratatui::{
backend::CrosstermBackend,
crossterm::{
event::{self, KeyCode, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
},
layout::{Alignment, Rect},
style::{Style, Stylize},
widgets::{List, Paragraph, Widget},
Frame, Terminal,
};
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");
}
struct ControlKeyboard {
}
impl Widget for ControlKeyboard {
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer)
where
Self: Sized {
let style = Style::new();
buf.set_string(0, 0, "F1 to cycle foreground - F2 to cycle background", style)
}
}
#[derive(Clone)]
struct AppState {
pub menu_items: Vec<String>,
pub selected_menu_item: i32,
pub system: Chip8Computer,
}
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: Chip8Computer {
..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| {
frame.render_widget(app.system.memory, frame.area());
// frame.render_widget(app.control_keyboard, area);
//render_cpu_state(app.system.clone(), frame);
// render_video_state(app.system.video_memory, frame);
// render_menu_list(app.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");
}
KeyCode::Char('q') => { break; }
_ => (),
},
_ => (),
}
}
}
}
// stdout().execute(LeaveAlternateScreen)?;
// disable_raw_mode()?;
Ok(())
}
#[cfg(test)]
mod test {
use emmaemu::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH};
use crate::*;
#[test]
fn blank_screen_renders_to_text() {
let test_video = Chip8Video::default();
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!(Chip8Video::new(blank_data).format_as_string(), 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!(Chip8Video::new(filled_data).format_as_string(), 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!(Chip8Video::new(grid_data).format_as_string(), expected_data);
}
}
*/
fn main() {
println!("Taxation is theft");
}
-87
View File
@@ -1,87 +0,0 @@
use std::default::Default;
use std::fs::DirEntry;
use std::time::Instant;
use gemmaemu::{
chip8::computer::Chip8Computer,
constants::{CHIP8_MEMORY_SIZE, CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH},
};
use imgui::*;
use ratatui::symbols::half_block;
use sys::{ImColor, ImVec2, ImVector_ImU32};
use rand::random;
use gemmaemu::chip8::system_memory::Chip8SystemMemory;
use support::emmagui_support::EmmaGui;
mod support;
struct UiState {
pub show_registers: bool,
pub show_memory: bool,
pub show_video: bool,
pub filename_to_load: String,
pub on_colour: ImColor32,
pub off_colour: ImColor32,
pub is_running: bool,
pub frame_time: f32
}
impl Clone for UiState {
fn clone(&self) -> Self {
UiState {
show_registers: self.show_registers,
show_memory: self.show_memory,
show_video: self.show_video,
filename_to_load: self.filename_to_load.to_string(),
on_colour: self.on_colour,
off_colour: self.off_colour,
is_running: self.is_running,
frame_time: self.frame_time
}
}
}
impl Default for UiState {
fn default() -> Self {
UiState {
show_registers: false,
show_memory: false,
show_video: true,
filename_to_load: String::new(),
on_colour: ImColor32::from_rgb(0xff, 0xff, 0x00),
off_colour: ImColor32::from_rgb(0x00, 0xff, 0xff),
is_running: false,
frame_time: 32.0
}
}
}
fn main() {
pretty_env_logger::init();
let mut system = Chip8Computer::default();
let mut ui_state = UiState::default();
let mut last_tick_time = Instant::now();
support::simple_init(file!(), move |_, ui| {
let current_time = Instant::now();
let time_since_last_tick = current_time.duration_since(last_tick_time).as_millis();
if ui_state.is_running && time_since_last_tick > ui_state.frame_time as u128 {
system.step_system();
last_tick_time = current_time;
}
EmmaGui::system_controls(&mut system, &mut ui_state, ui);
if ui_state.show_registers {
EmmaGui::registers_view(&system, ui);
}
if ui_state.show_video {
EmmaGui::video_display(&system, &ui_state, ui);
}
if ui_state.show_memory {
let active_instruction = system.registers.peek_pc();
EmmaGui::hex_memory_display(system.memory.clone(), (0x100, 0x10), active_instruction as i16, ui);
}
});
}
-40
View File
@@ -1,40 +0,0 @@
use ratatui::{style::Stylize, widgets::{List, Paragraph}, Frame};
use crate::{AppState, Chip8Computer, CHIP8_REGISTER_COUNT, CHIP8_VIDEO_MEMORY};
pub fn render_cpu_state(cpu_state: Chip8Computer, 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,
);
}
pub fn render_video_state(video_memory: [bool; CHIP8_VIDEO_MEMORY], frame: &mut Frame) {
}
View File
-240
View File
@@ -1,240 +0,0 @@
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::thread::sleep;
use std::time::Duration;
use imgui::{Condition, ImColor32, Ui};
use log::debug;
use gemmaemu::chip8::computer::Chip8Computer;
use gemmaemu::chip8::system_memory::Chip8SystemMemory;
use gemmaemu::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH};
use crate::UiState;
pub struct EmmaGui {}
const CELL_WIDTH: i32 = 5i32;
const CELL_HEIGHT: i32 = 5i32;
struct GuiFileList {}
impl GuiFileList {
pub fn display_path(root: PathBuf, selected_filename: &String, ui: &Ui) -> String {
let mut working_filename = selected_filename.clone();
ui.text(format!("Displaying {}", root.to_str().unwrap_or("Unable to parse path")));
for (index, entry) in std::fs::read_dir(root.as_path()).unwrap().enumerate() {
let filename = entry.unwrap().file_name();
let filename = filename.to_str().unwrap();
let mut working_select = ui.selectable_config(format!("{}", filename));
if filename == selected_filename {
working_select = working_select.selected(true);
}
if working_select.build() {
debug!("SELECTED {index} / {filename}");
working_filename = filename.to_string();
};
}
working_filename
}
}
impl EmmaGui {
pub fn video_display(system_to_control: &Chip8Computer, gui_state: &UiState, ui: &Ui) {
// draw area size
let draw_area_size = ui.io().display_size;
let cell_width = ((draw_area_size[0] as i32 / 64) * 6) / 10;
let cell_height = ((draw_area_size[1] as i32 / 32) * 6) / 10;
ui.window(format!("Display {cell_width}x{cell_height}"))
.size([300.0, 300.0], Condition::FirstUseEver)
.build(|| {
let origin = ui.cursor_screen_pos();
let fg = ui.get_window_draw_list();
ui.text("This is the video display.");
for current_row in 0..=31 {
for current_column in 0..=63 {
let x_offset = origin[0] as i32 + (current_column * cell_width);
let y_offset = origin[1] as i32 + (current_row * cell_height);
let current_origin = [x_offset as f32, y_offset as f32];
let current_limit = [(x_offset + cell_width) as f32, (y_offset + cell_height) as f32];
let memory_offset = (current_row * 64 + current_column) as u16;
let to_render = system_to_control.video_memory.peek(memory_offset);
let color: ImColor32 = if to_render {
gui_state.on_colour
} else {
gui_state.off_colour
};
fg.add_rect_filled_multicolor(current_origin, current_limit, color, color, color, color);
}
}
});
}
pub fn system_controls(system_to_control: &mut Chip8Computer, gui_state: &mut UiState, ui: &Ui) {
ui.window("!!!! CONTROLS !!!!")
.size([345.0, 200.0], Condition::FirstUseEver)
.build(|| {
/* ROM Lister */
let new_filename = GuiFileList::display_path(PathBuf::from("resources/roms"), &gui_state.filename_to_load, ui);
if !new_filename.is_empty() {
if new_filename != gui_state.filename_to_load {
println!("NEW FILENAME SELECTED -> {new_filename}");
gui_state.filename_to_load = new_filename;
}
if ui.button("Load Program") {
let mut buffer = Vec::new();
println!("PREPARING TO LOAD {}", gui_state.filename_to_load);
// let mut input_file = File::open(Path::new("./1-chip8-logo.ch8")).expect("put 1-chip8-logo.ch8 in this directory");
let mut input_file = File::open(Path::new(&("resources/roms/".to_string() + &gui_state.filename_to_load))).expect("put 1-chip8-logo.ch8 in this directory");
input_file.read_to_end(&mut buffer).expect("unable to read file");
system_to_control.load_bytes_to_memory(0x200, buffer.into());
}
}
ui.separator();
if ui.button("Step") {
system_to_control.step_system();
};
if ui.button("Run") {
gui_state.is_running = true;
println!("STARTING THE SYSTEM");
}
if ui.button("Stop") {
gui_state.is_running = false;
println!("STOPPING THE SYSTEM");
}
ui.same_line();
if ui.button("Reset") {
*system_to_control = Chip8Computer::new();
}
if ui.button("Dump Video Memory") {
println!("{}", system_to_control.dump_video_to_string());
}
ui.same_line();
if ui.button("Dump Keypad State") {
println!("{}", system_to_control.dump_keypad_to_string());
}
ui.same_line();
if ui.button("Dump Registers") {
println!("{}", system_to_control.dump_registers_to_string());
}
ui.separator();
ui.checkbox("Show Memory", &mut gui_state.show_memory);
ui.same_line();
ui.checkbox("Show Video", &mut gui_state.show_video);
ui.same_line();
ui.checkbox("Show Registers", &mut gui_state.show_registers);
});
}
pub fn registers_view(system: &Chip8Computer, ui: &Ui) {
ui.window("Registers")
.size([400.0, 500.0], Condition::FirstUseEver)
.build(|| {
ui.text("Registers");
for i in 1..0x10 {
ui.text(format!("V{:X}: {}", i, system.registers.peek(i)));
if i != 7 {
ui.same_line();
}
}
ui.text("");
ui.text(format!("I: {:03X}", system.registers.peek_i()));
ui.same_line();
ui.text(format!("ST: {:02X}", system.sound_timer.current()));
ui.same_line();
ui.text(format!("DT: {:02X}", system.delay_timer.current()));
ui.text(format!("PC: {:02X}", system.registers.peek_pc()));
ui.text(format!("SP: {:02X}", system.stack.depth()));
});
}
pub fn hex_memory_display(bytes: Chip8SystemMemory, position: (i32, i32), active: i16, ui: &Ui) {
let rows = position.0;
let cols = position.1;
ui.window("System Memory")
.size([400.0, 300.0], Condition::FirstUseEver)
.position([500.0, 300.0], Condition::Always)
.build(|| {
let mut current_x_hover: i32 = 0;
let mut current_y_hover: i32 = 0;
// display a block of data
for current_row in 0..rows {
ui.text(format!("{:02x}", current_row * cols));
ui.same_line();
for current_column in 0..cols {
let data_offset = current_row * cols + current_column;
let formatted_text = format!("{:02x}", bytes.peek(data_offset as u16));
let text_location = ui.cursor_screen_pos();
let text_size = ui.calc_text_size(formatted_text.clone());
let bounding_box = imgui::sys::ImVec2 {
x: text_location[0] + text_size[0],
y: text_location[1] + text_size[1],
};
let hovering = ui.is_mouse_hovering_rect(text_location, [bounding_box.x, bounding_box.y]);
let is_active = data_offset == active as i32;
ui.text_colored(if hovering {
[0., 1., 1., 1.]
} else if is_active {
[1., 0., 1., 1.]
} else {
[1., 1., 0., 1.]
}, formatted_text.clone());
// if we are hovering show that at the bottom...
if hovering {
// Optionally change the text color to indicate it's interactable
current_x_hover = current_column;
current_y_hover = current_row;
// Check if the left mouse button is clicked while hovering over the text
if ui.is_mouse_clicked(imgui::MouseButton::Left) {
println!("Offset: [{}] [0x{:02x}] Value: [{}]", data_offset, data_offset, formatted_text.clone());
// Perform any action here, e.g., call a function, trigger an event, etc.
}
}
// are we on the same line?
if current_column != (cols - 1) {
ui.same_line();
}
}
}
ui.text(format!("Offset 0x{:03x}", current_x_hover * cols + current_y_hover));
});
}
pub fn system_memory_render(memory: Chip8SystemMemory, ui: &Ui) {
ui.window("System Memory")
.size([300.0, 100.0], Condition::FirstUseEver)
.build(|| {
ui.text("Rendering System Memory Here");
let draw_list = ui.get_foreground_draw_list();
let mut idx = 0;
for row in 0..CHIP8_VIDEO_HEIGHT {
for column in 0..CHIP8_VIDEO_WIDTH {
let x_offset = column * CELL_WIDTH;
let y_offset = row * CELL_WIDTH;
let start_point = [x_offset as f32, y_offset as f32];
let end_point = [(x_offset + CELL_WIDTH) as f32,
(y_offset + CELL_HEIGHT) as f32
];
let memory_offset = (row * CHIP8_VIDEO_WIDTH) + column;
let target_color = if memory.peek(memory_offset as u16) == 0 {
ImColor32::BLACK
} else {
ImColor32::WHITE
};
draw_list.add_rect([x_offset as f32, y_offset as f32],
[(x_offset + CELL_WIDTH) as f32, (y_offset + CELL_HEIGHT) as f32],
target_color).build();
idx += 1;
}
}
});
}
}
-183
View File
@@ -1,183 +0,0 @@
use glium::glutin::surface::WindowSurface;
use glium::{Display, Surface};
use imgui::{Context, FontConfig, FontGlyphRanges, FontSource, Ui};
use imgui_glium_renderer::Renderer;
use imgui_winit_support::winit::dpi::LogicalSize;
use imgui_winit_support::winit::event::{Event, WindowEvent};
use imgui_winit_support::winit::event_loop::EventLoop;
use imgui_winit_support::winit::window::WindowBuilder;
use imgui_winit_support::{HiDpiMode, WinitPlatform};
use std::path::Path;
use std::time::Instant;
pub mod emmagui_support;
use copypasta::{ClipboardContext, ClipboardProvider};
use imgui::ClipboardBackend;
pub struct ClipboardSupport(pub ClipboardContext);
pub fn clipboard_init() -> Option<ClipboardSupport> {
ClipboardContext::new().ok().map(ClipboardSupport)
}
impl ClipboardBackend for ClipboardSupport {
fn get(&mut self) -> Option<String> {
self.0.get_contents().ok()
}
fn set(&mut self, text: &str) {
// ignore errors?
let _ = self.0.set_contents(text.to_owned());
}
}
pub const FONT_SIZE: f32 = 13.0;
#[allow(dead_code)] // annoyingly, RA yells that this is unusued
pub fn simple_init<F: FnMut(&mut bool, &mut Ui) + 'static>(title: &str, run_ui: F) {
init_with_startup(title, |_, _, _| {}, run_ui);
}
pub fn init_with_startup<FInit, FUi>(title: &str, mut startup: FInit, mut run_ui: FUi)
where
FInit: FnMut(&mut Context, &mut Renderer, &Display<WindowSurface>) + 'static,
FUi: FnMut(&mut bool, &mut Ui) + 'static,
{
let mut imgui = create_context();
let title = match Path::new(&title).file_name() {
Some(file_name) => file_name.to_str().unwrap(),
None => title,
};
let event_loop = EventLoop::new().expect("Failed to create EventLoop");
let builder = WindowBuilder::new()
.with_maximized(true)
.with_title(title);
// .with_inner_size(LogicalSize::new(1024, 768));
let (window, display) = glium::backend::glutin::SimpleWindowBuilder::new()
.set_window_builder(builder)
.build(&event_loop);
let mut renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer");
if let Some(backend) = clipboard_init() {
imgui.set_clipboard_backend(backend);
} else {
eprintln!("Failed to initialize clipboard");
}
let mut platform = WinitPlatform::init(&mut imgui);
{
let dpi_mode = if let Ok(factor) = std::env::var("IMGUI_EXAMPLE_FORCE_DPI_FACTOR") {
// Allow forcing of HiDPI factor for debugging purposes
match factor.parse::<f64>() {
Ok(f) => HiDpiMode::Locked(f),
Err(e) => panic!("Invalid scaling factor: {}", e),
}
} else {
HiDpiMode::Default
};
platform.attach_window(imgui.io_mut(), &window, dpi_mode);
}
let mut last_frame = Instant::now();
startup(&mut imgui, &mut renderer, &display);
event_loop
.run(move |event, window_target| match event {
Event::NewEvents(_) => {
let now = Instant::now();
imgui.io_mut().update_delta_time(now - last_frame);
last_frame = now;
}
Event::AboutToWait => {
platform
.prepare_frame(imgui.io_mut(), &window)
.expect("Failed to prepare frame");
window.request_redraw();
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
let ui = imgui.frame();
let mut run = true;
run_ui(&mut run, ui);
if !run {
window_target.exit();
}
let mut target = display.draw();
target.clear_color_srgb(1.0, 1.0, 1.0, 1.0);
platform.prepare_render(ui, &window);
let draw_data = imgui.render();
renderer
.render(&mut target, draw_data)
.expect("Rendering failed");
target.finish().expect("Failed to swap buffers");
}
Event::WindowEvent {
event: WindowEvent::Resized(new_size),
..
} => {
if new_size.width > 0 && new_size.height > 0 {
display.resize((new_size.width, new_size.height));
}
platform.handle_event(imgui.io_mut(), &window, &event);
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => window_target.exit(),
event => {
platform.handle_event(imgui.io_mut(), &window, &event);
}
})
.expect("EventLoop error");
}
/// Creates the imgui context
pub fn create_context() -> imgui::Context {
let mut imgui = Context::create();
// Fixed font size. Note imgui_winit_support uses "logical
// pixels", which are physical pixels scaled by the devices
// scaling factor. Meaning, 13.0 pixels should look the same size
// on two different screens, and thus we do not need to scale this
// value (as the scaling is handled by winit)
imgui.fonts().add_font(&[
FontSource::TtfData {
data: include_bytes!("../../../../resources/Roboto-Regular.ttf"),
size_pixels: FONT_SIZE,
config: Some(FontConfig {
// As imgui-glium-renderer isn't gamma-correct with
// it's font rendering, we apply an arbitrary
// multiplier to make the font a bit "heavier". With
// default imgui-glow-renderer this is unnecessary.
rasterizer_multiply: 1.5,
// Oversampling font helps improve text rendering at
// expense of larger font atlas texture.
oversample_h: 4,
oversample_v: 4,
..FontConfig::default()
}),
},
FontSource::TtfData {
data: include_bytes!("../../../../resources/mplus-1p-regular.ttf"),
size_pixels: FONT_SIZE,
config: Some(FontConfig {
// Oversampling font helps improve text rendering at
// expense of larger font atlas texture.
oversample_h: 4,
oversample_v: 4,
// Range of glyphs to rasterize
glyph_ranges: FontGlyphRanges::japanese(),
..FontConfig::default()
}),
},
]);
imgui.set_ini_filename(None);
imgui
}
+2 -7
View File
@@ -1,19 +1,14 @@
use log::{debug, error};
use log::{debug};
use crate::chip8::delay_timer::DelayTimer;
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
use crate::chip8::keypad::Keypad;
use crate::chip8::registers::Chip8Registers;
use crate::chip8::sound_timer::SoundTimer;
use crate::chip8::stack::Chip8Stack;
use crate::chip8::util::InstructionUtil;
use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT};
use super::{
cpu_states::Chip8CpuStates, instructions::Chip8CpuInstructions, system_memory::Chip8SystemMemory, video::Chip8Video,
};
const STACK_POINTER_DEFAULT: i16 = 0x100;
#[derive(Clone)]
pub struct Chip8Computer {
pub memory: Chip8SystemMemory,
@@ -85,7 +80,7 @@ impl Chip8Computer {
debug!("Stepping System 1 Step");
// read the next instruction
let mut working_instruction: u16 = 0b0000000000000000;
// let mut working_instruction: u16 = 0b0000000000000000;
let start_pc = self.registers.peek_pc();
let high_byte = (self.memory.clone().peek(start_pc) as u16).rotate_left(8);
let low_byte = self.memory.clone().peek(start_pc + 1) as u16;
+124 -36
View File
@@ -1,12 +1,10 @@
use std::ops::{Shl, Shr};
use imgui::ColorPicker3;
use std::ops::{BitAnd, Shl, Shr};
use log::debug;
use rand::random;
use crate::chip8::computer::{Chip8Computer};
use crate::chip8::cpu_states::Chip8CpuStates::WaitingForKey;
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
use crate::chip8::util::InstructionUtil;
use crate::chip8::video::Chip8Video;
/*
nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
@@ -137,7 +135,7 @@ impl Chip8CpuInstructions {
(0x2000 | (address & 0x0FFF)) as u16
}
Chip8CpuInstructions::SeVxByte(vx_register, byte) => {
(0x3000 | (vx_register << 8 | byte) as u16)
0x3000 | ((*vx_register as u16) << 8 | *byte as u16) as u16
}
Chip8CpuInstructions::SneVxByte(vx_register, byte) => {
0x4000u16 | (*vx_register as u16) << 8 | *byte as u16
@@ -418,7 +416,7 @@ impl Chip8CpuInstructions {
}
}
pub fn execute(&self, mut input: &mut Chip8Computer) -> Chip8Computer {
pub fn execute(&self, input: &mut Chip8Computer) -> Chip8Computer {
let start_pc = input.registers.peek_pc();
input.registers.poke_pc(start_pc + 2);
let _ = match self {
@@ -461,10 +459,18 @@ impl Chip8CpuInstructions {
}
// 0x4xkk Skip next instruction if Vx != kk
Chip8CpuInstructions::SneVxByte(x, byte) => {
if input.registers.peek(*x as u8) != *byte as u8 {
// 4xkk - SNE Vx, byte
// Skip next instruction if Vx != kk.
//
// The interpreter compares register Vx to kk, and if they are not equal,
// increments the program counter by 2.
let lhs = input.registers.peek(*x);
let rhs = *byte;
if lhs != rhs {
input.registers.advance_pc();
}
debug!("SneVxByte [0x{x:1x}] [0x{byte:2x}");
debug!("SneVxByte [0x{x:02x}] [0x{byte:02x}");
}
// 0x5xy0 Skip next instruction if Vx == Vy
Chip8CpuInstructions::SeVxVy(x, y) => {
@@ -527,37 +533,54 @@ impl Chip8CpuInstructions {
let lhs = input.registers.peek(*x as u8) as i16;
let rhs = input.registers.peek(*y as u8) as i16;
let working = lhs + rhs;
if working > 0xff {
input.registers.poke(0xf, 0x01);
}
input.registers.poke(*x as u8, working as u8);
if working >= 0x100 {
input.registers.poke(0xf, 0x01);
} else {
input.registers.poke(0x0f, 0x00);
}
}
Chip8CpuInstructions::SubVxVy(x, y) => {
// 8xy5 - SUB Vx, Vy
// Set Vx = Vx - Vy, set VF = NOT borrow.
//
// If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is subtracted from Vx, and the results stored in Vx.
let mut x_value: u16 = input.registers.peek(*x as u8) as u16;
let y_value = input.registers.peek(*y as u8);
// do we borrow?
if y_value >= x_value as u8 {
x_value += 256;
input.registers.poke(0xf, 1);
let lhs = input.registers.peek(*x);
let rhs = input.registers.peek(*y);
let mut result = 0;
let borrow_flag: u8 = if rhs > lhs {
result = (lhs as u16 + 0x100) - rhs as u16;
0
} else {
input.registers.poke(0xf, 0);
}
let result = (x_value - y_value as u16) as u8;
input.registers.poke(*x as u8, result);
result = lhs as u16 - rhs as u16;
1
};
input.registers.poke(*x as u8, result as u8);
input.registers.poke(0x0f, borrow_flag);
}
Chip8CpuInstructions::ShrVxVy(x, _) => {
// 8xy6 - SHR Vx {, Vy}
// Set Vx = Vx SHR 1.
//
// SHIFT 1 Bit ---->
// If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
let initial_value = input.registers.peek(*x as u8);
if 0xb1 & initial_value == 1 {
input.registers.poke(0xf, 1);
// overflow check
if initial_value.bitand(0b1) == 1 {
input.registers.poke(0x0f, 0x01);
} else {
input.registers.poke(0x0f, 0x00);
}
let rotated = initial_value >> 1;
println!("[{initial_value:80b}] / [{rotated:80b}]");
input.registers.poke(*x as u8, initial_value.shr(1));
}
Chip8CpuInstructions::SubnVxVy(x, y) => {
@@ -567,14 +590,17 @@ impl Chip8CpuInstructions {
// If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted from Vy, and the results stored in Vx.
let y_register = input.registers.peek(*y as u8);
let x_register = input.registers.peek(*x as u8);
let new_value = if y_register <= x_register { 1 } else { 0 };
let value_to_poke = if y_register <= x_register {
((y_register as u16 + 256) - x_register as u16) as u8
} else {
y_register - x_register
let mut value_to_poke = 0;
let new_value = if y_register <= x_register {
value_to_poke = (y_register as u16 + 256) - x_register as u16;
1 } else {
value_to_poke = (y_register - x_register) as u16;
0
};
input.registers.poke(0xf, new_value);
input.registers.poke(*x as u8, value_to_poke);
input.registers.poke(*x as u8, value_to_poke as u8);
}
Chip8CpuInstructions::ShlVxVy(x, _) => {
@@ -583,10 +609,13 @@ impl Chip8CpuInstructions {
//
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
let initial_value = input.registers.peek(*x as u8);
if 0x80 & initial_value == 0x80 {
input.registers.poke(0xf, 1);
let rotated = initial_value.shl(1);
if 0b10000000 & initial_value == 0b10000000 {
input.registers.poke(0x0f, 0x01);
} else {
input.registers.poke(0x0f, 0x00);
}
input.registers.poke(*x as u8, initial_value.shl(1));
input.registers.poke(*x as u8,rotated);
}
Chip8CpuInstructions::SneVxVy(vx_register, vy_register) => {
// 9xy0 - SNE Vx, Vy
@@ -624,7 +653,7 @@ impl Chip8CpuInstructions {
// which is then ANDed with the value kk.
// The results are stored in Vx.
let new_value: u8 = random();
input.registers.poke(*x as u8, (new_value & *byte as u8))
input.registers.poke(*x as u8, new_value & *byte as u8)
}
Chip8CpuInstructions::DrawVxVyNibble(y,x, n) => {
// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
@@ -762,8 +791,7 @@ impl Chip8CpuInstructions {
let units = to_convert % 10;
// Convert to BCD
let result = ((hundreds as u16) << 8) | units as u16;
(tens << 4) | units;
let result = ((hundreds as u16) << 8) | units as u16 | ((tens as u16) << 4) | units as u16;
// write them to the memory pointed to by I, I+1, and I+2
let target_start_offset = input.registers.peek_i();
input.memory.poke(target_start_offset, hundreds);
@@ -775,7 +803,6 @@ impl Chip8CpuInstructions {
//
// The interpreter copies the values of registers V0 through Vx into memory,
// starting at the address in I.
let num_loops = x;
let offset = input.registers.peek_i();
for i in 0..=*x {
input.memory.poke(offset + i as u16, input.registers.peek(i as u8));
@@ -1461,4 +1488,65 @@ mod test {
Chip8CpuInstructions::LdVxK(0x1).execute(&mut x);
assert!(matches!(x.state, WaitingForKey));
}
#[test]
fn series8xy4_corex_tests() {
/// 8xy4
/// Set Vx = Vx + Vy
/// Set VF=1 if Carry
///
// 1 + 1
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x02);
assert_eq!(x.registers.peek(0x0f), 0x00);
// 255+1
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xff);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x00);
assert_eq!(x.registers.peek(0x0f), 0x01);
// 128+192
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 128);
x.registers.poke(0x02, 192);
Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 64);
assert_eq!(x.registers.peek(0x0f), 1);
}
#[test]
fn series8xy6_corex_tests() {
// 8xy6 - SHR Vx {, Vy}
// Set Vx = Vx SHR 1.
//
// If the least-significant bit of Vx is 1, then VF is set to 1,
// otherwise 0. Then Vx is divided by 2.
let mut x = Chip8Computer::new();
// 0b10000000 -> 0b01000000
let start_value = 0b10000000;
let end_value = 0b01000000;
x.registers.poke(0x01, start_value);
Chip8CpuInstructions::ShrVxVy(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), end_value);
assert_eq!(x.registers.peek(0x0f), 0);
// 0b00000001 -> 0b00000000
let start_value = 0b00000001;
let end_value = 0b00000000;
let mut x = Chip8Computer::new();
let start_value = 1;
x.registers.poke(0x01, start_value);
Chip8CpuInstructions::ShrVxVy(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), end_value);
assert_eq!(x.registers.peek(0x0f), 1);
let end_value = start_value / 2;
assert_eq!(end_value, 0);
}
}
-3
View File
@@ -1,6 +1,3 @@
use imgui::Key;
#[derive(Clone, Copy)]
pub struct Keypad {
keys: [bool; 0x10],
-4
View File
@@ -1,5 +1,3 @@
use log::debug;
/// Registers. numbered 1-16 publicly.
/// Privately using zero base array so -1 to shift from pub to priv.
#[derive(Clone, Copy)]
@@ -7,7 +5,6 @@ pub struct Chip8Registers {
registers: [u8; 16],
i_register: u16,
pc: u16,
sp: u16,
}
impl Chip8Registers {
@@ -22,7 +19,6 @@ impl Default for Chip8Registers {
registers: [0x00; 16],
i_register: 0x00,
pc: 0x200,
sp: 0x100,
}
}
}
-2
View File
@@ -1,5 +1,3 @@
use std::{thread, time};
use beep::beep;
use log::trace;
#[derive(Clone, Copy)]
+2 -11
View File
@@ -1,11 +1,6 @@
use glium::RawUniformValue::Vec2;
use image::load;
use imgui::sys::ImColor;
use imgui::{ImColor32, Ui};
use log::{debug, trace};
use ratatui::{style::Style, widgets::Widget};
use log::{trace};
use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_WIDTH};
use crate::constants::{CHIP8_MEMORY_SIZE};
pub const CHIP8_PROGRAM_LOAD_OFFSET: i32 = 0x200;
pub const CHIP8FONT_0: [u8; 5] = [0xF0, 0x90, 0x90, 0x90, 0xF0];
@@ -41,10 +36,6 @@ impl Default for Chip8SystemMemory {
x
}
}
const cell_width: i32 = 5i32;
const cell_height: i32 = 5i32;
impl Chip8SystemMemory {
pub fn new() -> Self {
+1 -1
View File
@@ -55,7 +55,7 @@ impl InstructionUtil {
// 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 & 0x00FF)
instruction_to_read_from & 0x00FF
}
pub fn read_upper_byte_lower_nibble(to_read_from: u16) -> u16 {