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