Modified debugger so the input is in the frontend

The debug loop that reads a command and does something is part of the
frontend's main loop, so that it can potentially update, even though
it doesn't actually work for minifb because the command input is a
blocking call.  It's also not implemented in the pixels frontend.
At some point I'll make a web frontend.
This commit is contained in:
transistor 2023-06-10 15:28:21 -07:00
parent 18d1912f9a
commit 3bd4c24ea8
21 changed files with 467 additions and 266 deletions

107
Cargo.lock generated
View File

@ -48,6 +48,55 @@ dependencies = [
"winapi",
]
[[package]]
name = "anstream"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
[[package]]
name = "anstyle-parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "anstyle-wincon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
]
[[package]]
name = "atty"
version = "0.2.14"
@ -206,7 +255,7 @@ dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"clap_lex 0.2.4",
"indexmap",
"once_cell",
"strsim 0.10.0",
@ -214,6 +263,28 @@ dependencies = [
"textwrap 0.16.0",
]
[[package]]
name = "clap"
version = "4.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8f255e4b8027970e78db75e78831229c9815fdbfa67eb1a1b777a62e24b4a0"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acd4f3c17c83b0ba34ffbc4f8bbd74f079413f747f84a6f89292f138057e36ab"
dependencies = [
"anstream",
"anstyle",
"bitflags",
"clap_lex 0.5.0",
"strsim 0.10.0",
]
[[package]]
name = "clap_derive"
version = "3.2.25"
@ -236,6 +307,18 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "clap_lex"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "colored"
version = "2.0.0"
@ -469,6 +552,18 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "is-terminal"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "itoa"
version = "1.0.6"
@ -641,7 +736,7 @@ dependencies = [
name = "moa_console"
version = "0.1.0"
dependencies = [
"clap 3.2.25",
"clap 4.3.3",
"log",
"moa_common",
"moa_core",
@ -672,7 +767,7 @@ dependencies = [
name = "moa_minifb"
version = "0.1.0"
dependencies = [
"clap 3.2.25",
"clap 4.3.3",
"log",
"minifb",
"moa_common",
@ -1507,6 +1602,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "vec_map"
version = "0.8.2"

View File

@ -15,4 +15,5 @@ default-members = ["emulator/frontends/minifb"]
[profile.release]
debug = true
#overflow-checks = true

View File

@ -24,6 +24,7 @@ pub struct ClockDuration {
impl ClockDuration {
pub const ZERO: Self = Self::from_femtos(0);
pub const MAX: Self = Self::from_femtos(Femtos::MAX);
pub const FEMTOS_PER_SEC: Femtos = 1_000_000_000_000_000;
pub const FEMTOS_PER_MILLISEC: Femtos = 1_000_000_000_000;
@ -207,6 +208,7 @@ pub struct ClockTime(ClockDuration);
impl ClockTime {
pub const START: Self = Self(ClockDuration::ZERO);
pub const FOREVER: Self = Self(ClockDuration::MAX);
#[inline]
pub const fn as_duration(self) -> ClockDuration {

View File

@ -1,15 +1,23 @@
use std::io::Write;
use crate::error::Error;
use crate::system::System;
use crate::devices::{Address, Addressable, Debuggable, Device};
use crate::devices::{Address, Addressable};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DebugControl {
/// Wait for the next user command
Wait,
/// Continue looping without accepting input
Continue,
/// Exit the debugger and return to normal operation
Exit,
}
#[derive(Default)]
pub struct Debugger {
last_command: Option<String>,
repeat: u32,
repeat_command: Option<(u32, String)>,
trace_only: bool,
}
@ -19,45 +27,43 @@ impl Debugger {
self.trace_only = false;
}
pub fn run_debugger(&mut self, system: &System, target: Device) -> Result<(), Error> {
let mut target = target.borrow_mut();
let debug_obj = target.as_debuggable().unwrap();
pub fn print_step(&mut self, system: &mut System) -> Result<(), Error> {
println!("@ {} ns", system.clock.as_duration().as_nanos());
debug_obj.print_current_step(system)?;
if self.trace_only {
return Ok(());
}
if self.repeat > 0 {
self.repeat -= 1;
let last_command = self.last_command.clone().unwrap();
let args: Vec<&str> = vec![&last_command];
self.run_debugger_command(system, debug_obj, &args)?;
return Ok(());
}
loop {
let mut buffer = String::new();
std::io::stdout().write_all(b"> ").unwrap();
std::io::stdin().read_line(&mut buffer).unwrap();
let args: Vec<&str> = buffer.split_whitespace().collect();
match self.run_debugger_command(system, debug_obj, &args) {
Ok(true) => return Ok(()),
Ok(false) => { },
Err(err) => {
println!("Error: {}", err.msg);
},
}
if let Some(device) = system.get_next_debuggable_device() {
device.borrow_mut().as_debuggable().unwrap().print_current_step(system)?;
}
Ok(())
}
pub fn run_debugger_command(&mut self, system: &System, debug_obj: &mut dyn Debuggable, args: &[&str]) -> Result<bool, Error> {
if args.is_empty() {
// The Default Command
return Ok(true);
pub fn check_auto_command(&mut self, system: &mut System) -> Result<DebugControl, Error> {
if self.trace_only {
return Ok(DebugControl::Continue);
}
if let Some((count, command)) = self.repeat_command.take() {
self.run_command(system, &command)?;
let next_count = count - 1;
if next_count == 0 {
self.repeat_command = None;
} else {
self.repeat_command = Some((next_count, command));
}
return Ok(DebugControl::Continue);
}
Ok(DebugControl::Wait)
}
pub fn run_command(&mut self, system: &mut System, command: &str) -> Result<DebugControl, Error> {
let args: Vec<&str> = command.split_whitespace().collect();
// If no command given, then run the `step` command
let args = if args.is_empty() {
vec!["step"]
} else {
args
};
match args[0] {
"b" | "break" | "breakpoint" => {
if args.len() != 2 {
@ -71,8 +77,10 @@ impl Debugger {
println!("Breakpoint set for devices {:?} at {:08x}", name, addr);
},
None => {
debug_obj.add_breakpoint(addr);
println!("Breakpoint set for {:08x}", addr);
if let Some(device) = system.get_next_debuggable_device() {
device.borrow_mut().as_debuggable().unwrap().add_breakpoint(addr);
println!("Breakpoint set for {:08x}", addr);
}
},
}
}
@ -89,8 +97,10 @@ impl Debugger {
println!("Breakpoint removed for devices {:?} at {:08x}", name, addr);
},
None => {
debug_obj.remove_breakpoint(addr);
println!("Breakpoint removed for {:08x}", addr);
if let Some(device) = system.get_next_debuggable_device() {
device.borrow_mut().as_debuggable().unwrap().remove_breakpoint(addr);
println!("Breakpoint removed for {:08x}", addr);
}
},
}
}
@ -145,20 +155,23 @@ impl Debugger {
0x1000
};
debug_obj.print_disassembly(addr, count);
if let Some(device) = system.get_next_debuggable_device() {
device.borrow_mut().as_debuggable().unwrap().print_disassembly(addr, count);
}
},
"c" | "continue" => {
self.check_repeat_arg(args)?;
system.disable_debugging();
return Ok(true);
self.check_repeat_arg(&args)?;
return Ok(DebugControl::Exit);
},
"s" | "step" => {
self.check_repeat_arg(args)?;
return Ok(true);
self.check_repeat_arg(&args)?;
system.step_until_debuggable()?;
return Ok(DebugControl::Wait);
},
"t" | "trace" => {
self.trace_only = true;
return Ok(true);
system.step_until_debuggable()?;
return Ok(DebugControl::Continue);
}
"setb" | "setw" | "setl" => {
if args.len() != 3 {
@ -185,18 +198,20 @@ impl Debugger {
// return Ok(true);
//},
_ => {
if debug_obj.execute_command(system, args)? {
println!("Error: unknown command {}", args[0]);
if let Some(device) = system.get_next_debuggable_device() {
if device.borrow_mut().as_debuggable().unwrap().run_command(system, &args)? {
println!("Error: unknown command {}", args[0]);
}
}
},
}
Ok(false)
Ok(DebugControl::Wait)
}
fn check_repeat_arg(&mut self, args: &[&str]) -> Result<(), Error> {
if args.len() > 1 {
self.repeat = args[1].parse::<u32>().map_err(|_| Error::new("Unable to parse repeat number"))?;
self.last_command = Some(args[0].to_string());
let count = args[1].parse::<u32>().map_err(|_| Error::new("Unable to parse repeat number"))?;
self.repeat_command = Some((count, args[0].to_string()));
}
Ok(())
}

View File

@ -168,14 +168,12 @@ pub fn write_leu32(data: &mut [u8], value: u32) -> &mut [u8] {
/// A device (cpu) that can debugged using the built-in debugger
pub trait Debuggable {
fn debugging_enabled(&mut self) -> bool;
fn set_debugging(&mut self, enable: bool);
fn add_breakpoint(&mut self, addr: Address);
fn remove_breakpoint(&mut self, addr: Address);
fn print_current_step(&mut self, system: &System) -> Result<(), Error>;
fn print_disassembly(&mut self, addr: Address, count: usize);
fn execute_command(&mut self, system: &System, args: &[&str]) -> Result<bool, Error>;
fn run_command(&mut self, system: &System, args: &[&str]) -> Result<bool, Error>;
}
/// A device (peripheral) that can inspected using the built-in debugger
@ -219,7 +217,7 @@ pub fn wrap_transmutable<T: Transmutable + 'static>(value: T) -> TransmutableBox
static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DeviceId(usize);
impl DeviceId {
@ -247,6 +245,10 @@ impl Device {
Self(DeviceId::new(), wrap_transmutable(value))
}
pub fn id(&self) -> DeviceId {
self.0
}
pub fn borrow_mut(&self) -> RefMut<'_, Box<dyn Transmutable>> {
self.1.borrow_mut()
}
@ -254,33 +256,6 @@ impl Device {
pub fn try_borrow_mut(&self) -> Result<RefMut<'_, Box<dyn Transmutable>>, BorrowMutError> {
self.1.try_borrow_mut()
}
/*
#[inline]
pub fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
self.1.borrow_mut().as_steppable()
}
#[inline]
pub fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
None
}
#[inline]
pub fn as_interruptable(&mut self) -> Option<&mut dyn Interruptable> {
None
}
#[inline]
pub fn as_debuggable(&mut self) -> Option<&mut dyn Debuggable> {
None
}
#[inline]
pub fn as_inspectable(&mut self) -> Option<&mut dyn Inspectable> {
None
}
*/
}

View File

@ -15,7 +15,7 @@ pub mod host;
pub use log::{trace, debug, info, warn, error};
pub use crate::clock::{ClockTime, ClockDuration, Frequency};
pub use crate::debugger::Debugger;
pub use crate::debugger::{DebugControl, Debugger};
pub use crate::devices::{Address, Addressable, Steppable, Interruptable, Debuggable, Inspectable, Transmutable, TransmutableBox, Device};
pub use crate::devices::{read_beu16, read_beu32, read_leu16, read_leu32, write_beu16, write_beu32, write_leu16, write_leu32, wrap_transmutable};
pub use crate::error::{Error, ErrorType};

View File

@ -1,10 +1,9 @@
use std::rc::Rc;
use std::cell::{Cell, RefCell, RefMut};
use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
use crate::memory::Bus;
use crate::debugger::Debugger;
use crate::signals::EdgeSignal;
use crate::error::{Error, ErrorType};
use crate::interrupts::InterruptController;
@ -17,8 +16,6 @@ pub struct System {
pub devices: HashMap<String, Device>,
pub event_queue: Vec<NextStep>,
pub debug_enabled: Cell<bool>,
pub debugger: RefCell<Debugger>,
pub debuggables: Vec<Device>,
pub bus: Rc<RefCell<Bus>>,
@ -35,8 +32,6 @@ impl Default for System {
devices: HashMap::new(),
event_queue: vec![],
debug_enabled: Cell::new(false),
debugger: RefCell::new(Debugger::default()),
debuggables: Vec::new(),
bus: Rc::new(RefCell::new(Bus::default())),
@ -87,18 +82,6 @@ impl System {
Ok(())
}
pub fn enable_debugging(&self) {
self.debug_enabled.set(true);
for device in &self.debuggables {
device.borrow_mut().as_debuggable().map(|device| device.set_debugging(true));
}
self.debugger.borrow_mut().breakpoint_occurred();
}
pub fn disable_debugging(&self) {
self.debug_enabled.set(false);
}
fn process_one_event(&mut self) -> Result<(), Error> {
let mut event_device = self.event_queue.pop().unwrap();
self.clock = event_device.next_clock;
@ -114,28 +97,50 @@ impl System {
}
pub fn step(&mut self) -> Result<(), Error> {
self.check_debugger();
match self.process_one_event() {
Ok(()) => {
if self.get_bus().check_and_reset_watcher_modified() {
self.enable_debugging();
}
},
Ok(()) => {},
Err(err) if err.err == ErrorType::Breakpoint => {
println!("Breakpoint reached: {}", err.msg);
self.enable_debugging();
return Err(err);
},
Err(err) => {
self.exit_error();
println!("{:?}", err);
log::error!("{:?}", err);
return Err(err);
},
}
Ok(())
}
pub fn run_for(&mut self, elapsed: ClockDuration) -> Result<(), Error> {
pub fn step_until_device(&mut self, device: Device) -> Result<(), Error> {
loop {
self.step()?;
if self.get_next_event_device().id() == device.id() {
break;
}
}
Ok(())
}
pub fn step_until_debuggable(&mut self) -> Result<(), Error> {
loop {
self.step()?;
if self.get_next_event_device().borrow_mut().as_debuggable().is_some() {
break;
}
}
Ok(())
}
pub fn run_until_clock(&mut self, clock: ClockTime) -> Result<(), Error> {
while self.clock < clock {
self.step()?;
}
Ok(())
}
pub fn run_for_duration(&mut self, elapsed: ClockDuration) -> Result<(), Error> {
let target = self.clock + elapsed;
while self.clock < target {
@ -144,6 +149,11 @@ impl System {
Ok(())
}
pub fn run_forever(&mut self) -> Result<(), Error> {
self.run_until_clock(ClockTime::FOREVER)
}
// TODO rename this run_until_signal, and make it take a signal as argument
pub fn run_until_break(&mut self) -> Result<(), Error> {
let mut signal = match &self.break_signal {
Some(signal) => signal.clone(),
@ -156,10 +166,6 @@ impl System {
Ok(())
}
pub fn run_loop(&mut self) {
self.run_for(ClockDuration::from_nanos(u64::MAX)).unwrap();
}
pub fn exit_error(&mut self) {
for (_, dev) in self.devices.iter() {
if let Some(dev) = dev.borrow_mut().as_steppable() {
@ -168,23 +174,25 @@ impl System {
}
}
pub fn get_next_event_device(&self) -> Device {
self.event_queue[self.event_queue.len() - 1].device.clone()
}
pub fn get_next_debuggable_device(&self) -> Option<Device> {
for event in self.event_queue.iter().rev() {
if event.device.borrow_mut().as_debuggable().is_some() {
return Some(event.device.clone());
}
}
None
}
fn try_add_debuggable(&mut self, device: Device) {
if device.borrow_mut().as_debuggable().is_some() {
self.debuggables.push(device);
}
}
fn check_debugger(&mut self) {
if self.debug_enabled.get() {
let top = self.event_queue[self.event_queue.len() - 1].device.clone();
if top.borrow_mut().as_debuggable().map(|debug| debug.debugging_enabled()).unwrap_or(false) {
if let Err(err) = self.debugger.borrow_mut().run_debugger(self, top.clone()) {
println!("Error: {:?}", err);
}
}
}
}
fn try_queue_device(&mut self, device: Device) {
if device.borrow_mut().as_steppable().is_some() {
self.queue_device(NextStep::new(device));

View File

@ -22,36 +22,25 @@ impl StackTracer {
#[derive(Clone, Default)]
pub struct M68kDebugger {
pub enabled: bool,
pub breakpoints: Vec<u32>,
pub use_tracing: bool,
pub step_until_return: Option<usize>,
pub stack_tracer: StackTracer,
pub(crate) skip_breakpoint: usize,
pub(crate) breakpoints: Vec<u32>,
pub(crate) step_until_return: Option<usize>,
pub(crate) stack_tracer: StackTracer,
}
impl Debuggable for M68k {
fn debugging_enabled(&mut self) -> bool {
self.debugger.enabled
}
fn set_debugging(&mut self, enable: bool) {
self.debugger.enabled = enable;
}
fn add_breakpoint(&mut self, addr: Address) {
self.debugger.breakpoints.push(addr as u32);
self.debugger.enabled = true;
}
fn remove_breakpoint(&mut self, addr: Address) {
if let Some(index) = self.debugger.breakpoints.iter().position(|a| *a == addr as u32) {
self.debugger.breakpoints.remove(index);
self.debugger.enabled = !self.debugger.breakpoints.is_empty();
}
}
fn print_current_step(&mut self, _system: &System) -> Result<(), Error> {
self.decoder.decode_at(&mut self.port, true, self.state.pc)?;
let _ = self.decoder.decode_at(&mut self.port, true, self.state.pc);
self.decoder.dump_decoded(&mut self.port);
self.dump_state();
Ok(())
@ -62,7 +51,7 @@ impl Debuggable for M68k {
decoder.dump_disassembly(&mut self.port, addr as u32, count as u32);
}
fn execute_command(&mut self, system: &System, args: &[&str]) -> Result<bool, Error> {
fn run_command(&mut self, system: &System, args: &[&str]) -> Result<bool, Error> {
match args[0] {
"ds" | "stack" | "dumpstack" => {
println!("Stack:");
@ -80,19 +69,19 @@ impl Debuggable for M68k {
}
impl M68k {
#[allow(dead_code)]
pub fn enable_tracing(&mut self) {
self.debugger.use_tracing = true;
}
pub fn check_breakpoints(&mut self, system: &System) {
pub fn check_breakpoints(&mut self) -> Result<(), Error> {
for breakpoint in &self.debugger.breakpoints {
if *breakpoint == self.state.pc {
println!("Breakpoint reached: {:08x}", *breakpoint);
system.enable_debugging();
break;
if self.debugger.skip_breakpoint > 0 {
self.debugger.skip_breakpoint -= 1;
return Ok(());
} else {
self.debugger.skip_breakpoint = 1;
return Err(Error::breakpoint(format!("breakpoint reached: {:08x}", *breakpoint)));
}
}
}
Ok(())
}
}

View File

@ -94,11 +94,12 @@ impl M68k {
}
pub fn cycle_one(&mut self, system: &System) -> Result<ClockCycles, Error> {
self.check_breakpoints()?;
self.decode_next()?;
self.execute_current()?;
self.check_pending_interrupts(system)?;
self.check_breakpoints(system);
Ok(self.timing.calculate_clocks(false, 1))
}
@ -206,10 +207,6 @@ impl M68k {
self.timing.add_instruction(&self.decoder.instruction);
if self.debugger.use_tracing {
self.decoder.dump_decoded(&mut self.port);
}
self.state.pc = self.decoder.end;
Ok(())

View File

@ -8,28 +8,18 @@ use crate::instructions::Register;
#[derive(Clone, Default)]
pub struct Z80Debugger {
pub enabled: bool,
pub breakpoints: Vec<u16>,
pub(crate) skip_breakpoint: usize,
pub(crate) breakpoints: Vec<u16>,
}
impl Debuggable for Z80 {
fn debugging_enabled(&mut self) -> bool {
self.debugger.enabled
}
fn set_debugging(&mut self, enable: bool) {
self.debugger.enabled = enable;
}
fn add_breakpoint(&mut self, addr: Address) {
self.debugger.breakpoints.push(addr as u16);
self.debugger.enabled = true;
}
fn remove_breakpoint(&mut self, addr: Address) {
if let Some(index) = self.debugger.breakpoints.iter().position(|a| *a == addr as u16) {
self.debugger.breakpoints.remove(index);
self.debugger.enabled = !self.debugger.breakpoints.is_empty();
}
}
@ -45,7 +35,7 @@ impl Debuggable for Z80 {
decoder.dump_disassembly(&mut self.port, addr as u16, count as u16);
}
fn execute_command(&mut self, _system: &System, args: &[&str]) -> Result<bool, Error> {
fn run_command(&mut self, _system: &System, args: &[&str]) -> Result<bool, Error> {
match args[0] {
"l" => {
self.state.reg[Register::L as usize] = 0x05
@ -57,14 +47,19 @@ impl Debuggable for Z80 {
}
impl Z80 {
pub fn check_breakpoints(&mut self, system: &System) {
pub fn check_breakpoints(&mut self) -> Result<(), Error> {
for breakpoint in &self.debugger.breakpoints {
if *breakpoint == self.state.pc {
println!("Breakpoint reached: {:08x}", *breakpoint);
system.enable_debugging();
break;
if self.debugger.skip_breakpoint > 0 {
self.debugger.skip_breakpoint -= 1;
return Ok(());
} else {
self.debugger.skip_breakpoint = 1;
return Err(Error::breakpoint(format!("breakpoint reached: {:08x}", *breakpoint)));
}
}
}
Ok(())
}
}

View File

@ -76,7 +76,7 @@ impl Z80 {
Status::Init => self.init(),
Status::Halted => Err(Error::new("CPU stopped")),
Status::Running => {
match self.cycle_one(system) {
match self.cycle_one() {
Ok(clocks) => Ok(clocks),
Err(Error { err: ErrorType::Processor, .. }) => {
Ok(4)
@ -98,10 +98,11 @@ impl Z80 {
Ok(16)
}
pub fn cycle_one(&mut self, system: &System) -> Result<u16, Error> {
pub fn cycle_one(&mut self) -> Result<u16, Error> {
self.check_breakpoints()?;
self.decode_next()?;
self.execute_current()?;
self.check_breakpoints(system);
Ok(Z80InstructionCycles::from_instruction(&self.decoder.instruction)?
.calculate_cycles(self.executor.took_branch))
}

View File

@ -5,9 +5,9 @@ edition = "2021"
default-run = "moa-computie"
[dependencies]
log = "0.4"
clap = "3.2.20"
simple_logger = "2.3.0"
log = "^0.4"
clap = "^4"
simple_logger = "^2"
moa_core = { path = "../../core" }
moa_common = { path = "../common", features = ["tty"] }

View File

@ -3,14 +3,12 @@ use moa_console::ConsoleFrontend;
use moa_systems_computie::build_computie;
fn main() {
simple_logger::SimpleLogger::new()
.with_level(log::Level::Debug.to_level_filter())
.without_timestamps()
.init().unwrap();
let matches = ConsoleFrontend::args("Computie68k Emulator")
.get_matches();
let mut frontend = ConsoleFrontend;
let mut frontend = ConsoleFrontend::new();
let mut system = build_computie(&mut frontend).unwrap();
system.run_loop();
frontend.start(matches, system);
}

View File

@ -1,23 +1,23 @@
use clap::{App, Arg};
use clap::{Arg};
use moa_console::ConsoleFrontend;
use moa_systems_genesis::{build_genesis, SegaGenesisOptions};
fn main() {
let matches = App::new("Sega Genesis/Mega Drive Emulator")
let matches = ConsoleFrontend::args("Sega Genesis/Mega Drive Emulator")
.arg(Arg::new("ROM")
.help("ROM file to load (must be flat binary)"))
.get_matches();
let mut frontend = ConsoleFrontend;
let mut frontend = ConsoleFrontend::new();
let mut options = SegaGenesisOptions::default();
if let Some(filename) = matches.value_of("ROM") {
if let Some(filename) = matches.get_one::<String>("ROM") {
options.rom = filename.to_string();
}
let mut system = build_genesis(&mut frontend, options).unwrap();
system.run_loop();
let system = build_genesis(&mut frontend, options).unwrap();
frontend.start(matches, system);
}

View File

@ -1,5 +1,8 @@
use moa_core::Error;
use clap::{Command, Arg, ArgAction, ArgMatches};
use std::io::{self, Write};
use moa_core::{Error, ErrorType, System, ClockDuration, DebugControl, Debugger};
use moa_core::host::{Host, Tty, ControllerEvent, Audio, DummyAudio, FrameReceiver, EventSender};
pub struct ConsoleFrontend;
@ -26,3 +29,77 @@ impl Host for ConsoleFrontend {
}
}
impl ConsoleFrontend {
pub fn new() -> Self {
Self
}
pub fn args(application_name: &'static str) -> Command {
Command::new(application_name)
.arg(Arg::new("log-level")
.short('l')
.long("log-level")
.help("Set the type of log messages to print"))
.arg(Arg::new("debugger")
.short('d')
.long("debugger")
.action(ArgAction::SetTrue)
.help("Start the debugger before running machine"))
}
pub fn start(self, matches: ArgMatches, mut system: System) {
let log_level = match matches.get_one("log-level").map(|s: &String| s.as_str()) {
Some("trace") => log::Level::Trace,
Some("debug") => log::Level::Debug,
Some("info") => log::Level::Info,
Some("warn") => log::Level::Warn,
Some("error") => log::Level::Error,
_ => log::Level::Info,
};
// Start the logger
simple_logger::SimpleLogger::new()
.with_level(log_level.to_level_filter())
.without_timestamps()
.init()
.unwrap();
// Run the main loop
let mut debugger = Debugger::default();
let mut run_debugger = matches.get_flag("debugger");
loop {
if run_debugger {
run_debugger = false;
loop {
debugger.print_step(&mut system).unwrap();
if debugger.check_auto_command(&mut system).unwrap() == DebugControl::Continue {
continue;
}
let mut buffer = String::new();
io::stdout().write_all(b"> ").unwrap();
io::stdin().read_line(&mut buffer).unwrap();
match debugger.run_command(&mut system, &buffer) {
Ok(DebugControl::Exit) => break,
Ok(_) => {},
Err(err) => {
println!("Error: {}", err.msg);
},
}
}
}
match system.run_for_duration(ClockDuration::MAX - system.clock.as_duration()) {
Ok(()) => {},
Err(err) if err.err == ErrorType::Breakpoint => {
run_debugger = true;
},
Err(err) => {
panic!("{:?}", err);
},
}
}
}
}

View File

@ -6,9 +6,9 @@ default-run = "moa-genesis"
[dependencies]
log = "0.4"
minifb = "0.19"
clap = "3.2.20"
simple_logger = "2.3.0"
minifb = "^0.19"
clap = "^4"
simple_logger = "^2"
moa_core = { path = "../../core" }
moa_common = { path = "../common", features = ["audio"] }

View File

@ -10,7 +10,7 @@ fn main() {
.get_matches();
let mut options = SegaGenesisOptions::default();
if let Some(filename) = matches.value_of("ROM") {
if let Some(filename) = matches.get_one::<String>("ROM") {
options.rom = filename.to_string();
}

View File

@ -1,13 +1,13 @@
use std::thread;
use std::str::FromStr;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use minifb::{self, Key, MouseMode, MouseButton};
use clap::{App, Arg, ArgMatches};
use clap::{Command, Arg, ArgAction, ArgMatches};
use moa_core::{System, Error, ClockDuration, Device};
use moa_core::{System, Error, ErrorType, ClockDuration, Device, Debugger, DebugControl};
use moa_core::host::{Host, Audio, KeyEvent, MouseEvent, MouseState, ControllerDevice, ControllerEvent, EventSender, PixelEncoding, Frame, FrameReceiver};
use moa_common::{AudioMixer, AudioSource};
@ -24,34 +24,39 @@ const WIDTH: u32 = 320;
const HEIGHT: u32 = 224;
pub fn new(name: &str) -> App {
App::new(name)
pub fn new(name: &'static str) -> Command {
Command::new(name)
.arg(Arg::new("scale")
.short('s')
.long("scale")
.takes_value(true)
.help("Scale the screen"))
.arg(Arg::new("threaded")
.short('t')
.long("threaded")
.help("Run the simulation in a separate thread"))
.arg(Arg::new("speed")
.short('x')
.long("speed")
.takes_value(true)
.help("Adjust the speed of the simulation"))
.arg(Arg::new("threaded")
.short('t')
.long("threaded")
.action(ArgAction::SetTrue)
.help("Run the simulation in a separate thread"))
.arg(Arg::new("log-level")
.short('l')
.long("log-level")
.help("Set the type of log messages to print"))
.arg(Arg::new("debugger")
.short('d')
.long("debugger")
.action(ArgAction::SetTrue)
.help("Start the debugger before running machine"))
.arg(Arg::new("disable-audio")
.short('a')
.long("disable-audio")
.action(ArgAction::SetTrue)
.help("Disable audio output"))
}
pub fn run<I>(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result<System, Error> + Send + 'static {
if matches.occurrences_of("threaded") > 0 {
if matches.get_flag("threaded") {
run_threaded(matches, init);
} else {
run_inline(matches, init);
@ -75,7 +80,7 @@ pub fn run_threaded<I>(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFr
thread::spawn(move || {
let mut system = init(&mut frontend.lock().unwrap()).unwrap();
frontend.lock().unwrap().finalize();
system.run_loop();
system.run_forever().unwrap();
});
}
@ -203,18 +208,22 @@ impl MiniFrontend {
}
pub fn start(&mut self, matches: ArgMatches, mut system: Option<System>) {
let log_level = match matches.get_one("log-level").map(|s: &String| s.as_str()) {
Some("trace") => log::Level::Trace,
Some("debug") => log::Level::Debug,
Some("info") => log::Level::Info,
Some("warn") => log::Level::Warn,
Some("error") => log::Level::Error,
_ => log::Level::Warn,
};
simple_logger::SimpleLogger::new()
.with_level(log::Level::Warn.to_level_filter())
.with_level(log_level.to_level_filter())
.without_timestamps()
.init().unwrap();
.init()
.unwrap();
if matches.occurrences_of("debugger") > 0 {
if let Some(system) = system.as_mut() {
system.enable_debugging();
}
}
if self.mixer.borrow_mut().num_sources() != 0 && matches.occurrences_of("disable-audio") == 0 {
if self.mixer.borrow_mut().num_sources() != 0 && !matches.get_flag("disable-audio") {
if let Some(system) = system.as_mut() {
system.add_device("mixer", Device::new(self.mixer.clone())).unwrap();
}
@ -222,7 +231,7 @@ impl MiniFrontend {
}
let options = minifb::WindowOptions {
scale: match matches.value_of("scale").map(|s| s.parse::<u8>().unwrap()) {
scale: match matches.get_one::<String>("scale").map(|s| s.parse::<u8>().unwrap()) {
Some(1) => minifb::Scale::X1,
Some(2) => minifb::Scale::X2,
Some(4) => minifb::Scale::X4,
@ -232,10 +241,7 @@ impl MiniFrontend {
..Default::default()
};
let speed = match matches.value_of("speed") {
Some(x) => f32::from_str(x).unwrap(),
None => 1.0,
};
let speed = matches.get_one::<f32>("speed").cloned().unwrap_or(1.0);
let mut size = (WIDTH, HEIGHT);
if let Some(queue) = self.video.as_mut() {
@ -257,21 +263,49 @@ impl MiniFrontend {
window.limit_update_rate(Some(Duration::from_micros(16600)));
//let nanoseconds_per_frame = (16_600_000 as f32 * speed) as u64;
let mut debugger = Debugger::default();
let mut run_debugger = matches.get_flag("debugger");
let mut update_timer = Instant::now();
let mut last_frame = Frame::new(size.0, size.1, PixelEncoding::ARGB);
while window.is_open() && !window.is_key_down(Key::Escape) {
let frame_time = update_timer.elapsed();
update_timer = Instant::now();
//println!("new frame after {:?}us", frame_time.as_micros());
if run_debugger {
if let Some(mut system) = system.as_mut() {
debugger.print_step(&mut system).unwrap();
if debugger.check_auto_command(&mut system).unwrap() != DebugControl::Continue {
let mut buffer = String::new();
io::stdout().write_all(b"> ").unwrap();
io::stdin().read_line(&mut buffer).unwrap();
match debugger.run_command(&mut system, &buffer) {
Ok(DebugControl::Exit) => {
run_debugger = false;
},
Ok(_) => {},
Err(err) => {
println!("Error: {}", err.msg);
},
}
}
}
} else {
let frame_time = update_timer.elapsed();
update_timer = Instant::now();
//println!("new frame after {:?}us", frame_time.as_micros());
//let run_timer = Instant::now();
if let Some(system) = system.as_mut() {
//system.run_for(nanoseconds_per_frame).unwrap();
system.run_for(ClockDuration::from_nanos((frame_time.as_nanos() as f32 * speed) as u64)).unwrap();
//system.run_until_break().unwrap();
//let run_timer = Instant::now();
if let Some(system) = system.as_mut() {
//system.run_for(nanoseconds_per_frame).unwrap();
match system.run_for_duration(ClockDuration::from_nanos((frame_time.as_nanos() as f32 * speed) as u64)) {
Ok(()) => {},
Err(err) if err.err == ErrorType::Breakpoint => {
run_debugger = true;
},
Err(err) => panic!("{:?}", err),
}
//system.run_until_break().unwrap();
}
//let sim_time = run_timer.elapsed().as_micros();
//println!("ran simulation for {:?}us in {:?}us (avg: {:?}us)", frame_time.as_micros(), sim_time, frame_time.as_micros() as f64 / sim_time as f64);
}
//let sim_time = run_timer.elapsed().as_micros();
//println!("ran simulation for {:?}us in {:?}us (avg: {:?}us)", frame_time.as_micros(), sim_time, frame_time.as_micros() as f64 / sim_time as f64);
if let Some(keys) = window.get_keys_pressed(minifb::KeyRepeat::No) {
for key in keys {
@ -279,9 +313,7 @@ impl MiniFrontend {
// Process special keys
if let Key::D = key {
if let Some(system) = system.as_ref() {
system.enable_debugging();
}
run_debugger = true;
}
}
}
@ -306,7 +338,7 @@ impl MiniFrontend {
}
if let Some(queue) = self.video.as_mut() {
if let Some((clock, frame)) = queue.latest() {
if let Some((_clock, frame)) = queue.latest() {
last_frame = frame
}
window.update_with_buffer(&last_frame.bitmap, last_frame.width as usize, last_frame.height as usize).unwrap();

View File

@ -140,7 +140,7 @@ pub fn run_system_for(handle: &mut SystemHandle, nanos: u32) -> usize {
let run_timer = Instant::now();
let nanoseconds_per_frame = ClockDuration::from_nanos(nanos as u64);
//let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as Clock;
if let Err(err) = handle.0.run_for(nanoseconds_per_frame) {
if let Err(err) = handle.0.run_for_duration(nanoseconds_per_frame) {
log::error!("{:?}", err);
}
let run_time = run_timer.elapsed().as_millis();
@ -267,7 +267,7 @@ fn update(emulator: Rc<RefCell<Emulator>>) {
let diff = run_timer.duration_since(last_update);
let nanoseconds_per_frame = ClockDuration::from_nanos(diff.as_nanos() as u64);
//let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as Clock;
if let Err(err) = emulator.borrow_mut().system.run_for(nanoseconds_per_frame) {
if let Err(err) = emulator.borrow_mut().system.run_for_duration(nanoseconds_per_frame) {
log::error!("{:?}", err);
}
let run_time = run_timer.elapsed().as_millis();
@ -285,7 +285,7 @@ fn update(emulator: Rc<RefCell<Emulator>>) {
fn update(emulator: Rc<RefCell<Emulator>>) {
let run_timer = Instant::now();
let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as u64;
if let Err(err) = emulator.borrow_mut().system.run_for(ClockDuration::from_nanos(nanoseconds_per_frame)) {
if let Err(err) = emulator.borrow_mut().system.run_for_duration(ClockDuration::from_nanos(nanoseconds_per_frame)) {
log::error!("{:?}", err);
}
log::info!("ran simulation for {:?}ms in {:?}ms", nanoseconds_per_frame / 1_000_000, run_timer.elapsed().as_millis());

View File

@ -3,7 +3,7 @@ use std::mem;
use std::rc::Rc;
use std::cell::RefCell;
use moa_core::{System, Error, Frequency, Signal, MemoryBlock, Bus, BusPort, Address, Addressable, Debuggable, Device};
use moa_core::{System, Error, Frequency, Signal, MemoryBlock, Bus, BusPort, Address, Addressable, Device};
use moa_core::host::Host;
use moa_m68k::{M68k, M68kType};
@ -82,8 +82,7 @@ pub fn build_genesis<H: Host>(host: &mut H, mut options: SegaGenesisOptions) ->
coproc_bus.borrow_mut().insert(0x6000, coproc_register.clone());
coproc_bus.borrow_mut().insert(0x7f11, coproc_sn_sound.clone());
coproc_bus.borrow_mut().insert(0x8000, coproc_area);
let mut coproc = Z80::new(Z80Type::Z80, Frequency::from_hz(3_579_545), BusPort::new(0, 16, 8, coproc_bus), None);
coproc.set_debugging(true);
let coproc = Z80::new(Z80Type::Z80, Frequency::from_hz(3_579_545), BusPort::new(0, 16, 8, coproc_bus), None);
let mut reset = coproc.reset.clone();
let mut bus_request = coproc.bus_request.clone();
reset.set(true);

View File

@ -1,11 +1,24 @@
* I want to make some kind of memory transaction object that does everything in a contained but logical way, including handling exception
information needed about the last access, and adjusting the pre/post inc/dec
* rename System::run_until_break into run_until_signal, and make it take a signal as argument
* make errors be enums to make for easier matching
* test m68k cycle timing again
* fix tests
* you really need a full web-based debugger
* breakpoints should be in the CPU implementation, and get checked before an instruction starts executing
* there should be a list of "debuggable" objects in the system to make it easier to list them and iterate over them
* a breakpoint should escape the system and return to the frontend, which should handle everything (not inline because the frontend won't update)
* the debug dump things should not used the clocked addressing, but use a debugging mode thing of some kind so as not to influence the sim state
* the way you're doing debugging is so bad, and something's broken with the Z80
* debugger should return a breakpoint error to the frontend, so that the frontend still runs, instead of suspending the current execution
* can you make the debugger workable in the web ui in some way? So you can have a debug window open while playing the game or something
* the way you're doing debugging is so bad, and something's broken with the Z80
* the debug dump things should not used the clocked addressing, but use a debugging mode thing of some kind so as not to influence the sim state
* there needs to be a better way of finding devices, and getting names/refs out of them
* debugger should return a breakpoint error to the frontend, so that the frontend still runs, instead of suspending the current execution
* for 68k impl, I want to make some kind of memory transaction object that does everything in a contained but logical way, including handling exception
information needed about the last access, and adjusting the pre/post inc/dec
* I like making address adapters like this (below)
* you could have busport take a closure or something which translates the address, and returns an error that will be passed up if it occurs
@ -50,9 +63,9 @@
* AudioFrame (and possibly the mixer and source) should be moved to the core, it should probably have the sample rate
* split AudioOutput into a sender and receiver
* can you eliminate the source-to-mixer queues?
* add audio support to the console, but it needs to be conditionally compilable so that audio can be disabled (computie doesn't need it, only genesis)
* should you rename devices.rs traits.rs?
* the interrupt controller stuff is really not good. It should be more like busport, and connected to a device at startup (eg. create
interrupt controller, then create objects that use that controller and pass in values, maybe an option so that the controller doesn't
have to be hooked up, meaning hardware interrupts would not be used.
@ -60,10 +73,7 @@
transmutable abstraction, so that you can avoid the need for explicit borrows
* add rust runtime checks for math to look for overflow errors
* I think the overflowing add and subs return the original number and not the overflowed result. I might have already checked that
in the m68k impl but I should check again
* double check the functioning of the banked areas and register settings for Z80 coprocessor
* test the Z80 more, add tests like jsmoo's
* add opentelemetry if it can be wasm compatible, or some kind of timing for giving an average framerate
* improve performance
@ -76,7 +86,6 @@
* get rustfmt, rustdoc, and clippy working in some kind of semi-automatic fashion
* add ability to serialize/deserialize state into something, so it can be restored... (maybe not worth it though)
* can you make the debugger more accessible, so a web interface could access the data and display it, in light of the fact that println isn't available in wasm
@ -100,6 +109,8 @@ System/Traits:
of taking up a whole range of addresses
* you could modify read()/write() in Addressable to return the number of bytes read or written for dynamic bus sizing used by the MC68020+
* add ability to serialize/deserialize state into something, so it can be restored... (maybe not worth it though)
Debugger:
@ -115,12 +126,13 @@ Debugger:
Genesis/Mega Drive:
* there is an issue with Mortal Kombat 2 where it will crash randomly at the start of a fight. The code is actually swapping
stacks a bunch of times, and at some point, the stack is corrupted or something and it `rts`s to the wrong address...
* the 68000/Z80 bank switching is probably buggy
* the H/V counters are not accurate because it seems to count at different speeds in the blanking period (time vs return value numbers don't divide properly)
* make the ym7101 set/reset the v_int occurred flag based on the interrupt controller
* there is an issue with Mortal Kombat 2 where it will crash randomly at the start of a fight. The code is actually swapping
stacks a bunch of times, and at some point, the stack is corrupted or something and it `rts`s to the wrong address...
* add support for the sprite overflow flag (low priority)
* still possibly a bug with the shadow/highlight colours
@ -143,8 +155,7 @@ Macintosh:
Z80:
* add instruction timings to Z80
* unimplemented: CPD, CPDR, CPI, CPIR, DAA, IND, INDR, INI, INIR, INic, INx, OTDR, OTIR, OUTD, OUTI, OUTic, OUTx, RETI, RETN, RLD, RRD
* unimplemented: CPD, CPDR, CPI, CPIR, DAA, IND, INDR, INI, INIR, INic, INx, OTDR, OTIR, OUTD, OUTI, OUTic, OUTx
Synth: