diff --git a/frontends/moa-minifb/Cargo.toml b/frontends/moa-minifb/Cargo.toml index 7d11a0c..1aa10eb 100644 --- a/frontends/moa-minifb/Cargo.toml +++ b/frontends/moa-minifb/Cargo.toml @@ -9,4 +9,5 @@ default-run = "moa-genesis" [dependencies] moa = { path = "../../" } minifb = "0.19" +clap = "3.0.0-beta.5" diff --git a/frontends/moa-minifb/src/bin/moa-genesis.rs b/frontends/moa-minifb/src/bin/moa-genesis.rs index ef381e8..6d31948 100644 --- a/frontends/moa-minifb/src/bin/moa-genesis.rs +++ b/frontends/moa-minifb/src/bin/moa-genesis.rs @@ -1,9 +1,14 @@ + +use moa_minifb; use moa::machines::genesis::build_genesis; -use moa_minifb::{run_inline, run_threaded}; fn main() { - //run_inline(build_genesis); - run_threaded(build_genesis); + let matches = moa_minifb::new("Sega Genesis/Mega Drive Emulator") + .get_matches(); + + moa_minifb::run(matches, |frontends| { + build_genesis(frontend) + }); } diff --git a/frontends/moa-minifb/src/bin/moa-trs80.rs b/frontends/moa-minifb/src/bin/moa-trs80.rs index b805fc2..aebb99a 100644 --- a/frontends/moa-minifb/src/bin/moa-trs80.rs +++ b/frontends/moa-minifb/src/bin/moa-trs80.rs @@ -1,9 +1,19 @@ -use moa::machines::trs80::build_trs80; -use moa_minifb::{run_inline, run_threaded}; +use moa_minifb; +use moa::machines::trs80::{build_trs80, Trs80Options}; fn main() { - //run_inline(build_trs80); - run_threaded(build_trs80); + let matches = moa_minifb::new("TRS-80 Emulator") + .arg("-r, --rom=[FILE] 'ROM file to load at the start of memory'") + .get_matches(); + + let mut options = Trs80Options::new(); + if let Some(filename) = matches.value_of("rom") { + options.rom = filename.to_string(); + } + + moa_minifb::run(matches, |frontend| { + build_trs80(frontend, options) + }); } diff --git a/frontends/moa-minifb/src/lib.rs b/frontends/moa-minifb/src/lib.rs index cf75014..3a7e5e3 100644 --- a/frontends/moa-minifb/src/lib.rs +++ b/frontends/moa-minifb/src/lib.rs @@ -1,24 +1,72 @@ -mod keys; - use std::thread; use std::time::Duration; use std::sync::{Arc, Mutex}; use minifb::{self, Key}; +use clap::{App, ArgMatches}; use moa::error::Error; use moa::system::System; use moa::host::traits::{Host, JoystickDevice, JoystickUpdater, KeyboardUpdater, WindowUpdater}; +mod keys; use crate::keys::map_key; -//const WIDTH: usize = 320; -//const HEIGHT: usize = 224; +const WIDTH: u32 = 320; +const HEIGHT: u32 = 224; + + +pub fn new(name: &str) -> App { + App::new(name) + .arg("-s, --scale=[1,2,4] 'Scale the screen'") + .arg("-t, --threaded 'Run the simulation in a separate thread'") +} + +pub fn run(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result + Send + 'static { + if matches.value_of("threaded").is_some() { + run_inline(matches, init); + } else { + run_threaded(matches, init); + } +} + +pub fn run_inline(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result { + let mut frontend = MiniFrontendBuilder::new(); + let system = init(&mut frontend).unwrap(); + + frontend + .build() + .start(matches, Some(system)); +} + +pub fn run_threaded(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result + Send + 'static { + let frontend = Arc::new(Mutex::new(MiniFrontendBuilder::new())); + + { + let frontend = frontend.clone(); + thread::spawn(move || { + let mut system = init(&mut *(frontend.lock().unwrap())).unwrap(); + frontend.lock().unwrap().finalize(); + system.run_loop(); + }); + } + + wait_until_initialized(frontend.clone()); + + frontend + .lock().unwrap() + .build() + .start(matches, None); +} + +fn wait_until_initialized(frontend: Arc>) { + while frontend.lock().unwrap().finalized == false { + thread::sleep(Duration::from_millis(10)); + } +} -const WIDTH: usize = 384; -const HEIGHT: usize = 128; pub struct MiniFrontendBuilder { pub window: Option>, @@ -90,21 +138,32 @@ pub struct MiniFrontend { impl MiniFrontend { pub fn new(window: Option>, joystick: Option>, keyboard: Option>) -> Self { Self { - buffer: vec![0; WIDTH * HEIGHT], + buffer: vec![0; (WIDTH * HEIGHT) as usize], window, joystick, keyboard, } } - pub fn start(&mut self, mut system: Option) { + pub fn start(&mut self, matches: ArgMatches, mut system: Option) { let mut options = minifb::WindowOptions::default(); - options.scale = minifb::Scale::X2; + options.scale = match matches.value_of("scale").map(|s| u8::from_str_radix(s, 10).unwrap()) { + Some(1) => minifb::Scale::X1, + Some(2) => minifb::Scale::X2, + Some(4) => minifb::Scale::X4, + _ => minifb::Scale::X2, + }; + + let mut size = (WIDTH, HEIGHT); + if let Some(updater) = self.window.as_mut() { + size = updater.get_size(); + self.buffer = vec![0; (size.0 * size.1) as usize]; + } let mut window = minifb::Window::new( "Test - ESC to exit", - WIDTH, - HEIGHT, + size.0 as usize, + size.1 as usize, options, ) .unwrap_or_else(|e| { @@ -144,47 +203,10 @@ impl MiniFrontend { } if let Some(updater) = self.window.as_mut() { - updater.update_frame(WIDTH as u32, HEIGHT as u32, &mut self.buffer); - window.update_with_buffer(&self.buffer, WIDTH, HEIGHT).unwrap(); + updater.update_frame(size.0, size.1, &mut self.buffer); + window.update_with_buffer(&self.buffer, size.0 as usize, size.1 as usize).unwrap(); } } } } - -pub fn run_inline(init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result { - let mut frontend = MiniFrontendBuilder::new(); - let system = init(&mut frontend).unwrap(); - - frontend - .build() - .start(Some(system)); -} - -pub fn run_threaded(init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result + Send + 'static { - let frontend = Arc::new(Mutex::new(MiniFrontendBuilder::new())); - - { - let frontend = frontend.clone(); - thread::spawn(move || { - let mut system = init(&mut *(frontend.lock().unwrap())).unwrap(); - frontend.lock().unwrap().finalize(); - system.run_loop(); - }); - } - - wait_until_initialized(frontend.clone()); - - frontend - .lock().unwrap() - .build() - .start(None); -} - -fn wait_until_initialized(frontend: Arc>) { - while frontend.lock().unwrap().finalized == false { - thread::sleep(Duration::from_millis(10)); - } -} - - diff --git a/src/host/gfx.rs b/src/host/gfx.rs index 86d7629..b780671 100644 --- a/src/host/gfx.rs +++ b/src/host/gfx.rs @@ -18,7 +18,7 @@ impl BlitableSurface for Frame { self.bitmap.resize((width * height) as usize, 0); } - fn blit>(&mut self, pos_x: u32, mut pos_y: u32, mut bitmap: B, width: u32, height: u32) { + fn blit>(&mut self, pos_x: u32, pos_y: u32, mut bitmap: B, width: u32, height: u32) { for y in pos_y..(pos_y + height) { for x in pos_x..(pos_x + width) { match bitmap.next().unwrap() { @@ -61,6 +61,10 @@ impl FrameSwapper { } impl WindowUpdater for FrameSwapper { + fn get_size(&mut self) -> (u32, u32) { + (self.current.width, self.current.height) + } + fn update_frame(&mut self, width: u32, height: u32, bitmap: &mut [u32]) { std::mem::swap(&mut self.current, &mut self.previous); @@ -75,6 +79,10 @@ impl WindowUpdater for FrameSwapper { pub struct FrameSwapperWrapper(Arc>); impl WindowUpdater for FrameSwapperWrapper { + fn get_size(&mut self) -> (u32, u32) { + self.0.lock().map(|mut swapper| swapper.get_size()).unwrap_or((0, 0)) + } + fn update_frame(&mut self, width: u32, height: u32, bitmap: &mut [u32]) { self.0.lock().map(|mut swapper| swapper.update_frame(width, height, bitmap)); } diff --git a/src/host/traits.rs b/src/host/traits.rs index b69733c..6905940 100644 --- a/src/host/traits.rs +++ b/src/host/traits.rs @@ -26,6 +26,7 @@ pub trait Tty { } pub trait WindowUpdater: Send { + fn get_size(&mut self) -> (u32, u32); fn update_frame(&mut self, width: u32, height: u32, bitmap: &mut [u32]); } diff --git a/src/machines/trs80.rs b/src/machines/trs80.rs index 9a64e63..5bf78d4 100644 --- a/src/machines/trs80.rs +++ b/src/machines/trs80.rs @@ -9,23 +9,40 @@ use crate::peripherals::trs80; use crate::host::traits::Host; +pub struct Trs80Options { + pub rom: String, + pub memory: u16, + pub frequency: u32, +} -pub fn build_trs80(host: &mut H) -> Result { +impl Trs80Options { + pub fn new() -> Self { + Self { + rom: "binaries/trs80/level2.rom".to_string(), + memory: 0xC000, + frequency: 1_774_000, + } + } +} + + +pub fn build_trs80(host: &mut H, options: Trs80Options) -> Result { let mut system = System::new(); let mut rom = MemoryBlock::new(vec![0; 0x4000]); - rom.load_at(0x0000, "binaries/trs80/level1.rom")?; + //rom.load_at(0x0000, "binaries/trs80/level1.rom")?; //rom.load_at(0x0000, "binaries/trs80/level2.rom")?; + rom.load_at(0x0000, &options.rom)?; rom.read_only(); system.add_addressable_device(0x0000, wrap_transmutable(rom))?; - let ram = MemoryBlock::new(vec![0; 0xC000]); + let ram = MemoryBlock::new(vec![0; options.memory as usize]); system.add_addressable_device(0x4000, wrap_transmutable(ram))?; let model1 = trs80::model1::Model1Peripherals::create(host)?; system.add_addressable_device(0x37E0, wrap_transmutable(model1)).unwrap(); - let mut cpu = Z80::new(Z80Type::Z80, 4_000_000, BusPort::new(0, 16, 8, system.bus.clone())); + let mut cpu = Z80::new(Z80Type::Z80, options.frequency, BusPort::new(0, 16, 8, system.bus.clone())); //cpu.add_breakpoint(0x0); //cpu.add_breakpoint(0xb55); //cpu.add_breakpoint(0xb76); @@ -46,9 +63,9 @@ pub fn build_trs80(host: &mut H) -> Result { //cpu.add_breakpoint(0xc77); //cpu.add_breakpoint(0xc83); //cpu.add_breakpoint(0x96d); - cpu.add_breakpoint(0x970); - cpu.add_breakpoint(0x9e2); - cpu.add_breakpoint(0x9f9); + //cpu.add_breakpoint(0x970); + //cpu.add_breakpoint(0x9e2); + //cpu.add_breakpoint(0x9f9); system.add_interruptable_device("cpu", wrap_transmutable(cpu))?; diff --git a/todo.txt b/todo.txt index dd53622..3d4c6fa 100644 --- a/todo.txt +++ b/todo.txt @@ -1,28 +1,8 @@ -* there is a problem where something writes to the rom area which causes a crash -At 0x16cde, a move writes an invalid value to 0 via an indirect %a2 reg. The value of the reg might have changed during an interrupt, but it definitely breaks when the next interrupt occurs -Before the loop is 0x16a0e which then calculates the count and such -0x16584 is where the memory address 0xffd11a is updated, which is then used for the bad 0x0000 address which causes the improper write. 0x16570 is a better start -On broken cycle: %a1 = 1df40, moves that location + 1 to %d0 - - -* 0x1650e is where 0xffac08 is changed to 0xd100 -* 0x16572 is where 0xffd100 is changed to 0 -* what if it's a problem when turning the 0 into a full address (which should be ff0000 or ffff00 but instead ends up being 000000) - - - - * the overflow bit is only correct for addition but not subtraction... in subtraction, two positives can result in a negative and vice versa - - - - -* fix ym7101 to better handle V/H interrupts (right now it sets and then the next step will clear, but it'd be nice if it could 'edge trigger') - * could have a remapper device, which takes a big swath of addresses in and maps them to another set of addresses (for Mac VIA generic to bus-hookup-in-mac adapter) * how can you do devices that change their address map during operation, like mac which puts rom at 0 and ram at 600000 temporarily * i need a better way of handling disperate reads/writes to I/O spaces, rather than having multiple devices or having a massive chunk of address space allocated, continuously @@ -32,36 +12,44 @@ On broken cycle: %a1 = 1df40, moves that location + 1 to %d0 So both could share the same Signal, one setting it and the other reading it, but how would you actually configure/build that? +* make it possible to specify the rom on the command line. Would this be machine specific? +* make the frontend resize its window based on the frame swapper * make it possible to set the frame sizes when creating the frame swapper * should you rename devices.rs traits.rs? -* implement a Z80 * maybe see about a Mac 128k or something -* add instruction timing to M68k -* YM7101 timing is causing it to be very slow... speeding this up increasing rendering speed a lot, even though the frame shouldn't be drawn that often... not sure what's wrong with the timing -* make the ym7101 set/reset the v_int occurred flag based on the interrupt controller - * you could modify read()/write() in Addressable to return the number of bytes read or written for dynamic bus sizing used by the MC68020+ * should you simulate bus arbitration? - -* make tests for each instruction -* check all instructions in the docs - -* unimplemented: ABCD, ADDX, BFFFO, BFINS, BKPT, CHK, EXG, ILLEGAL, MOVEfromCCR, MOVEP, RTR, RTD, SBCD, SUBX -* >=MC68020 undecoded & unimplemented: CALLM, CAS, CAS2, CHK2, CMP2, RTM, PACK, TRAPcc, UNPK - -* add support for MMU -* add support for FPU -* Coprocessor instructions: cpBcc, cpDBcc, cpGEN, cpScc, cpTRAPcc +Genesis/Mega Drive: + * fix ym7101 to better handle V/H interrupts (right now it sets and then the next step will clear, but it'd be nice if it could 'edge trigger') + * YM7101 timing is causing it to be very slow... speeding this up increasing rendering speed a lot, even though the frame shouldn't be drawn that often... not sure what's wrong with the timing + * make the ym7101 set/reset the v_int occurred flag based on the interrupt controller -* how can you have multple CPUs +68000: + * add instruction timing to M68k + * make tests for each instruction + * check all instructions in the docs + + * unimplemented: ABCD, ADDX, BFFFO, BFINS, BKPT, CHK, EXG, ILLEGAL, MOVEfromCCR, MOVEP, RTR, RTD, SBCD, SUBX + * >=MC68020 undecoded & unimplemented: CALLM, CAS, CAS2, CHK2, CMP2, RTM, PACK, TRAPcc, UNPK + + * add support for MMU + * add support for FPU + * Coprocessor instructions: cpBcc, cpDBcc, cpGEN, cpScc, cpTRAPcc + +Z80: + * add instruction timings to Z80 + + + +* how can you have multiple CPUs * each device that can make a bus request should have a BusPort which is used to access the bus * can you eventually make the system connections all configurable via a config file?