Skip to content

Commit

Permalink
tui: Highlight CPU register parts on change
Browse files Browse the repository at this point in the history
  • Loading branch information
Granddave committed Jan 2, 2024
1 parent ab44ac0 commit 511fdde
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 44 deletions.
110 changes: 89 additions & 21 deletions src/emulator/tui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,73 @@ use crate::emulator::{
memory::{Bus, Memory},
};

#[derive(Debug, Default, Clone)]
pub struct StateValue<T> {
value: T,
did_change: bool,
}

impl<T: std::cmp::PartialEq> StateValue<T> {
pub fn set(&mut self, value: T) {
if self.value == value {
return;
}
self.value = value;
self.did_change = true;
}

pub fn get(&self) -> T
where
T: Copy,
{
self.value
}

pub fn has_changed(&self) -> bool {
self.did_change
}

pub fn invalidate(&mut self) {
self.did_change = false;
}
}

#[derive(Debug, Default, Clone)]
pub struct EmulationState {
pub a: StateValue<u8>,
pub x: StateValue<u8>,
pub y: StateValue<u8>,
pub pc: StateValue<u16>,
pub sp: StateValue<u8>,

// Status register
pub carry: StateValue<bool>,
pub zero: StateValue<bool>,
pub interrupt_disable: StateValue<bool>,
pub decimal: StateValue<bool>,
pub break_command: StateValue<bool>,
pub overflow: StateValue<bool>,
pub negative: StateValue<bool>,
}

impl EmulationState {
fn invalidate(&mut self) {
self.a.invalidate();
self.x.invalidate();
self.y.invalidate();
self.pc.invalidate();
self.sp.invalidate();

self.carry.invalidate();
self.zero.invalidate();
self.interrupt_disable.invalidate();
self.decimal.invalidate();
self.break_command.invalidate();
self.overflow.invalidate();
self.negative.invalidate();
}
}

pub struct App {
cpu: Cpu,
memory: Memory,
Expand All @@ -12,6 +79,8 @@ pub struct App {
/// The start address of the program in memory.
program_start: u16,

state: EmulationState,

should_quit: bool,
}

Expand All @@ -22,6 +91,7 @@ impl App {
memory: Memory::new(),
program: program.to_vec(),
program_start,
state: EmulationState::default(),
should_quit: false,
};
app.reset();
Expand All @@ -41,6 +111,7 @@ impl App {

self.cpu = Cpu::new();
self.cpu.reset();
self.state.invalidate();
}

/// Quits the application.
Expand All @@ -51,6 +122,7 @@ impl App {
/// Steps the CPU by one instruction.
pub fn step_cpu(&mut self) {
self.cpu.step(&mut self.memory);
self.state.invalidate();
}

/// Run CPU execution until a break instruction is reached.
Expand All @@ -59,28 +131,24 @@ impl App {
.run(&mut self.memory, RunOption::StopOnBreakInstruction);
}

/// Get the status of the CPU in a string format.
pub fn status(&self) -> String {
let mut output = String::new();

/// Get the last and current state of the emulation
pub fn state(&mut self) -> EmulationState {
let regs = self.cpu.registers();
self.state.a.set(regs.a);
self.state.x.set(regs.x);
self.state.y.set(regs.y);
self.state.pc.set(regs.pc);
self.state.sp.set(regs.sp);

let status = regs.status;
self.state.carry.set(status.carry);
self.state.zero.set(status.zero);
self.state.interrupt_disable.set(status.interrupt_disable);
self.state.decimal.set(status.decimal);
self.state.break_command.set(status.break_command);
self.state.overflow.set(status.overflow);
self.state.negative.set(status.negative);

output.push_str("Registers:\n");
output.push_str(&format!("A: 0x{:02x}\n", regs.a));
output.push_str(&format!("X: 0x{:02x}\n", regs.x));
output.push_str(&format!("Y: 0x{:02x}\n", regs.y));
output.push_str(&format!("PC: 0x{:04x}\n", regs.pc));
output.push_str(&format!("SP: 0x{:02x}\n", regs.sp));
output.push_str(&format!("S: {:08b}\n", u8::from(regs.status)));
output.push_str(" NV-BDIZC\n");

// output.push_str("------------");
// output.push_str("Stack:");
// self.memory.dump(
// STACK_BASE + self.cpu.stack_pointer() as u16,
// STACK_BASE + STACK_POINTER_START as u16,
// );

output
self.state.clone()
}
}
139 changes: 116 additions & 23 deletions src/emulator/tui/ui.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,123 @@
use ratatui::{
prelude::{Alignment, Frame},
style::{Color, Style},
widgets::{Block, BorderType, Borders, Paragraph},
};
use ratatui::{prelude::*, widgets::*};
// use ratatui::{
// prelude::{Alignment, Frame},
// style::{Color, Style},
// text::Span,
// widgets::{Block, BorderType, Borders, Paragraph},
// };

use super::app::App;
use super::app::{App, StateValue};

fn style_state_text<T: std::cmp::PartialEq>(state: &StateValue<T>) -> Style {
if state.has_changed() {
Style::default().bold()
} else {
Style::default()
}
}

pub fn render(app: &mut App, frame: &mut Frame) {
let state = app.state();

let text: Vec<Line<'_>> = vec![
"Press `Esc`, `Ctrl-C` or `q` to stop running.".into(),
"Press `s` to step and `c` to run continuously until BRK instruction".into(),
"Press `r` to reset the CPU and memory".into(),
"".into(),
vec![
Span::raw("A: "),
Span::styled(
format!("0x{:02x}", state.a.get()),
style_state_text(&state.a),
),
]
.into(),
vec![
Span::raw("X: "),
Span::styled(
format!("0x{:02x}", state.x.get()),
style_state_text(&state.x),
),
]
.into(),
vec![
Span::raw("Y: "),
Span::styled(
format!("0x{:02x}", state.y.get()),
style_state_text(&state.y),
),
]
.into(),
vec![
Span::raw("PC: "),
Span::styled(
format!("0x{:04x}", state.pc.get()),
style_state_text(&state.pc),
),
]
.into(),
vec![
Span::raw("SP: "),
Span::styled(
format!("0x{:02x}", state.sp.get()),
style_state_text(&state.sp),
),
]
.into(),
vec![
Span::raw("S: 0b"),
Span::styled(
format!("{:1b}", state.negative.get() as u8),
style_state_text(&state.negative),
),
Span::styled(
format!("{:1b}", state.overflow.get() as u8),
style_state_text(&state.overflow),
),
Span::raw("0"),
Span::styled(
format!("{:1b}", state.break_command.get() as u8),
style_state_text(&state.break_command),
),
Span::styled(
format!("{:1b}", state.decimal.get() as u8),
style_state_text(&state.decimal),
),
Span::styled(
format!("{:1b}", state.interrupt_disable.get() as u8),
style_state_text(&state.interrupt_disable),
),
Span::styled(
format!("{:1b}", state.zero.get() as u8),
style_state_text(&state.zero),
),
Span::styled(
format!("{:1b}", state.carry.get() as u8),
style_state_text(&state.carry),
),
]
.into(),
Line::styled(" NV-BDIZC", Style::default().italic()),
];

frame.render_widget(
Paragraph::new(format!(
"Press `Esc`, `Ctrl-C` or `q` to stop running.\n\
Press `s` to step and `c` to run continuously until BRK instruction\n\
Press `r` to reset the CPU and memory\n\
\n\
{}",
app.status()
))
.block(
Block::default()
.title("6502 Emulator")
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_type(BorderType::Rounded),
)
.style(Style::default().fg(Color::Yellow))
.alignment(Alignment::Left),
Paragraph::new(text)
.block(
Block::default()
.title("6502 Emulator")
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_type(BorderType::Rounded),
)
.style(Style::default().fg(Color::Yellow))
.alignment(Alignment::Left),
frame.size(),
)

// output.push_str("------------");
// output.push_str("Stack:");
// self.memory.dump(
// STACK_BASE + self.cpu.stack_pointer() as u16,
// STACK_BASE + STACK_POINTER_START as u16,
// );
}

0 comments on commit 511fdde

Please sign in to comment.