From d274186388889476bbff6fd77187100ea45bb3af Mon Sep 17 00:00:00 2001 From: transistor Date: Mon, 6 Dec 2021 15:04:08 -0800 Subject: [PATCH] Added Macintosh 128k/512k work in progress --- binaries/macintosh/Macintosh 512k.rom | Bin 0 -> 65536 bytes frontends/moa-minifb/src/bin/moa-macintosh.rs | 13 ++ src/machines/macintosh.rs | 130 +++++++++++ src/machines/mod.rs | 1 + src/peripherals/macintosh/iwm.rs | 115 ++++++++++ src/peripherals/macintosh/mainboard.rs | 213 ++++++++++++++++++ src/peripherals/macintosh/mod.rs | 5 + src/peripherals/macintosh/video.rs | 82 +++++++ src/peripherals/mod.rs | 6 + src/peripherals/mos6522.rs | 121 ++++++++++ src/peripherals/z8530.rs | 57 +++++ src/signals.rs | 24 +- todo.txt | 1 + 13 files changed, 762 insertions(+), 6 deletions(-) create mode 100644 binaries/macintosh/Macintosh 512k.rom create mode 100644 frontends/moa-minifb/src/bin/moa-macintosh.rs create mode 100644 src/machines/macintosh.rs create mode 100644 src/peripherals/macintosh/iwm.rs create mode 100644 src/peripherals/macintosh/mainboard.rs create mode 100644 src/peripherals/macintosh/mod.rs create mode 100644 src/peripherals/macintosh/video.rs create mode 100644 src/peripherals/mos6522.rs create mode 100644 src/peripherals/z8530.rs diff --git a/binaries/macintosh/Macintosh 512k.rom b/binaries/macintosh/Macintosh 512k.rom new file mode 100644 index 0000000000000000000000000000000000000000..2d6ca7f8b44ac5e649be04ce3719af1062d0bd2a GIT binary patch literal 65536 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
U@#f(hg&(l{ew!P55QPd$C7PC%>~z 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 E0wpm1y#N3J literal 0 HcmV?d00001 diff --git a/frontends/moa-minifb/src/bin/moa-macintosh.rs b/frontends/moa-minifb/src/bin/moa-macintosh.rs new file mode 100644 index 0000000..b2c12e2 --- /dev/null +++ b/frontends/moa-minifb/src/bin/moa-macintosh.rs @@ -0,0 +1,13 @@ + +use moa_minifb; +use moa::machines::macintosh::build_macintosh_512k; + +fn main() { + let matches = moa_minifb::new("Macintosh 512k Emulator") + .get_matches(); + + moa_minifb::run(matches, |frontend| { + build_macintosh_512k(frontend) + }); +} + diff --git a/src/machines/macintosh.rs b/src/machines/macintosh.rs new file mode 100644 index 0000000..851fc3a --- /dev/null +++ b/src/machines/macintosh.rs @@ -0,0 +1,130 @@ + +use crate::error::Error; +use crate::system::System; +use crate::devices::{wrap_transmutable, Debuggable}; +use crate::memory::{MemoryBlock, AddressAdapter, BusPort}; + +use crate::cpus::m68k::{M68k, M68kType}; +use crate::peripherals::mos6522::Mos6522; +use crate::peripherals::z8530::Z8530; +use crate::peripherals::macintosh::iwm::IWM; +use crate::peripherals::macintosh::video::MacVideo; +use crate::peripherals::macintosh::mainboard::Mainboard; + +use crate::host::traits::Host; + + +pub fn build_macintosh_512k(host: &mut H) -> Result { + let mut system = System::new(); + + /* + let mut ram = MemoryBlock::new(vec![0; 0x00100000]); + ram.load_at(0, "binaries/macintosh/Macintosh 128k.rom")?; + let boxed_ram = wrap_transmutable(ram); + //system.add_addressable_device(0x00000000, boxed_ram.clone())?; + //system.add_addressable_device(0x00600000, boxed_ram)?; + + let mut rom = MemoryBlock::load("binaries/macintosh/Macintosh 128k.rom")?; + rom.read_only(); + let boxed_rom = wrap_transmutable(rom); + //system.add_addressable_device(0x00400000, wrap_transmutable(rom))?; + + + // The ROM accesses 0xf80000 to look for the debugger, and then accesses 0xf0000 which is the phase area + let misc = MemoryBlock::new(vec![0; 0x100000]); + system.add_addressable_device(0x00f00000, wrap_transmutable(misc))?; + + let video = MacVideo::create(host)?; + system.add_device("video", wrap_transmutable(video)).unwrap(); + + let scc1 = Z8530::new(); + //launch_terminal_emulator(serial.port_a.connect(Box::new(SimplePty::open()?))?); + //launch_slip_connection(serial.port_b.connect(Box::new(SimplePty::open()?))?); + system.add_addressable_device(0x009FFFF0, wrap_transmutable(scc1))?; + + let scc2 = Z8530::new(); + //launch_terminal_emulator(serial.port_a.connect(Box::new(SimplePty::open()?))?); + //launch_slip_connection(serial.port_b.connect(Box::new(SimplePty::open()?))?); + system.add_addressable_device(0x00BFFFF0, wrap_transmutable(scc2))?; + + let iwm = IWM::new(); + let adapter = AddressAdapter::new(wrap_transmutable(iwm), 9); + system.add_addressable_device(0x00DFE1FF, wrap_transmutable(adapter))?; + + //let via = wrap_transmutable(Mos6522::new()); + let mainboard = Mainboard::new(boxed_ram, boxed_rom); + system.add_addressable_device(0x00000000, mainboard.bus.clone())?; + let mainboard_boxed = wrap_transmutable(mainboard); + system.add_device("via", mainboard_boxed.clone())?; + let adapter = AddressAdapter::new(mainboard_boxed, 9); + system.add_addressable_device(0x00EFE000, wrap_transmutable(adapter))?; + */ + + let mut ram = MemoryBlock::new(vec![0; 0x00080000]); + let mut rom = MemoryBlock::load("binaries/macintosh/Macintosh 512k.rom")?; + rom.read_only(); + + let video = MacVideo::create(host)?; + system.add_device("video", wrap_transmutable(video)).unwrap(); + + let mainboard = Mainboard::create(wrap_transmutable(ram), wrap_transmutable(rom))?; + system.add_addressable_device(0x00000000, wrap_transmutable(mainboard))?; + + + let mut cpu = M68k::new(M68kType::MC68000, 7_833_600, BusPort::new(0, 24, 16, system.bus.clone())); + + //cpu.enable_tracing(); + //system.enable_debugging(); + //cpu.add_breakpoint(0x10781a); + //cpu.add_breakpoint(0x40002a); + //cpu.add_breakpoint(0x400694); // Ram Test + + //cpu.add_breakpoint(0x400170); // Failed, loops infinitely + cpu.add_breakpoint(0x4000f4); // Failed, should show the sad mac + //cpu.add_breakpoint(0x4006ae); + //cpu.add_breakpoint(0x400706); + //cpu.add_breakpoint(0x400722); // end of ram test + + //cpu.add_breakpoint(0x40026c); // System Initialization + //cpu.add_breakpoint(0x402adc); + //cpu.add_breakpoint(0x40078e); + //cpu.add_breakpoint(0x40080a); + + //cpu.add_breakpoint(0x400448); + //cpu.add_breakpoint(0x40040a); // InitROMCore (set up trap dispatch table) + //cpu.add_breakpoint(0x402acc); // InitMem + + //cpu.add_breakpoint(0x40045c); + //cpu.add_breakpoint(0x400614); // Start of InitIO + cpu.add_breakpoint(0x40062a); // Loop in InitIO + //cpu.add_breakpoint(0x400648); + //cpu.add_breakpoint(0x40064c); + //cpu.add_breakpoint(0x4014a6); // DrvrInstall + //cpu.add_breakpoint(0x401262); // $A000 handler, which is where the rom write happens + //cpu.add_breakpoint(0x4012ec); + //cpu.add_breakpoint(0x40133a); + + // Issue of writing to 0x100000 which doesn't exist + cpu.add_breakpoint(0x400d62); + + cpu.add_breakpoint(0x400464); // Boot Screen + + /* + use crate::devices::Addressable; + use crate::cpus::m68k::state::M68kState; + for i in 0..=65535 { + cpu.state = M68kState::new(); + system.get_bus().write_beu16(0, i).unwrap(); + match cpu.decode_next(&system) { + Ok(()) => { println!("TestCase {{ cpu: M68kType::MC68000, data: &[{:#06X}]\tins: Some({:?}) }},", i, cpu.decoder.instruction); }, + Err(_) => { println!("TestCase {{ cpu: M68kType::MC68000, data: &[{:#06X}]\tins: None }},", i); }, + } + } + panic!(""); + */ + + system.add_interruptable_device("cpu", wrap_transmutable(cpu))?; + + Ok(system) +} + diff --git a/src/machines/mod.rs b/src/machines/mod.rs index 05819db..1078069 100644 --- a/src/machines/mod.rs +++ b/src/machines/mod.rs @@ -1,5 +1,6 @@ pub mod computie; pub mod genesis; +pub mod macintosh; pub mod trs80; diff --git a/src/peripherals/macintosh/iwm.rs b/src/peripherals/macintosh/iwm.rs new file mode 100644 index 0000000..652acec --- /dev/null +++ b/src/peripherals/macintosh/iwm.rs @@ -0,0 +1,115 @@ + +use crate::error::Error; +use crate::system::System; +use crate::devices::{ClockElapsed, Address, Addressable, Steppable, Transmutable}; + + +const CA0: u8 = 0x01; +const CA1: u8 = 0x02; +const CA2: u8 = 0x04; +const LSTRB: u8 = 0x08; +const ENABLE: u8 = 0x10; +const SELECT: u8 = 0x20; +const Q6: u8 = 0x40; +const Q7: u8 = 0x80; + +const DEV_NAME: &'static str = "iwm"; + +pub struct IWM { + pub state: u8, + pub mode: u8, + pub handshake: u8, +} + +impl IWM { + pub fn new() -> Self { + Self { + state: 0, + mode: 0, + handshake: 0, + } + } + + pub fn flip_switches(&mut self, addr: Address) { + let mask = 1 << (addr >> 1); + + if (addr & 0x01) != 0 { + self.state |= mask; + } else { + self.state &= !mask; + } + info!("{}: state is now {:x}", DEV_NAME, self.state); + } +} + +impl Addressable for IWM { + fn len(&self) -> usize { + 0x10 + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + self.flip_switches(addr); + + if (addr & 0x01) != 0 { + data[0] = 0xFF; + return Ok(()); + } + + let i = data.len() - 1; + match self.state & (Q7 | Q6) { + 0 => { + // read data register + data[0] = 0xFF; + }, + Q6 => { + // read "status" register + data[i] = (self.mode & 0x1F) | if (self.state & ENABLE) != 0 { 0x20 } else { 0x00 }; + }, + Q7 => { + // read "write-handshake" register + data[i] = 0x3F | self.handshake; + } + b if b == (Q7 | Q6) => { + panic!(""); + }, + _ => { + warning!("{}: !!! unhandled read of {:0x} with state {:x}", DEV_NAME, addr, self.state); + }, + } + info!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data); + Ok(()) + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + self.flip_switches(addr); + + info!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]); + + let i = data.len() - 1; + match self.state & (Q7 | Q6 | ENABLE) { + b if b == (Q7 | Q6 | ENABLE) => { + self.handshake &= !0x80; + }, + b if b == (Q7 | Q6) => { + // write the mode register + self.mode = data[i] & 0x1f; + }, + _ => { + warning!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr); + }, + } + Ok(()) + } +} + + +impl Transmutable for IWM { + fn as_addressable(&mut self) -> Option<&mut dyn Addressable> { + Some(self) + } + + //fn as_steppable(&mut self) -> Option<&mut dyn Steppable> { + // Some(self) + //} +} + diff --git a/src/peripherals/macintosh/mainboard.rs b/src/peripherals/macintosh/mainboard.rs new file mode 100644 index 0000000..4706574 --- /dev/null +++ b/src/peripherals/macintosh/mainboard.rs @@ -0,0 +1,213 @@ + +use std::rc::Rc; +use std::cell::RefCell; + +use crate::memory::Bus; +use crate::error::Error; +use crate::system::System; +use crate::signals::Signal; +use crate::devices::{Clock, ClockElapsed, Address, Addressable, Steppable, Transmutable, TransmutableBox, wrap_transmutable}; + +use crate::peripherals::z8530::Z8530; +use crate::peripherals::mos6522::Mos6522; +use crate::peripherals::macintosh::iwm::IWM; +use crate::peripherals::macintosh::video::MacVideo; + +const DEV_NAME: &'static str = "mac"; + + +pub struct Mainboard { + pub lower_bus: Rc>, + pub scc1: Z8530, + pub scc2: Z8530, + pub iwm: IWM, + pub via: Mos6522, + pub phase_read: PhaseRead, + pub last_sec: Clock, +} + +impl Mainboard { + pub fn create(ram: TransmutableBox, rom: TransmutableBox) -> Result { + let scc1 = Z8530::new(); + let scc2 = Z8530::new(); + let iwm = IWM::new(); + let via = Mos6522::new(); + let phase_read = PhaseRead::new(); + + let lower_bus = Rc::new(RefCell::new(Bus::new())); + let ram_len = ram.borrow_mut().as_addressable().unwrap().len(); + let rom_len = rom.borrow_mut().as_addressable().unwrap().len(); + + let mainboard = Self { + lower_bus: lower_bus.clone(), + scc1, + scc2, + iwm, + via, + phase_read, + last_sec: 0, + }; + + mainboard.via.port_a.set_observer(move |port| { + if (port.data & 0x10) == 0 { + println!("{}: overlay is 0 (normal)", DEV_NAME); + lower_bus.borrow_mut().blocks.clear(); + lower_bus.borrow_mut().insert(0x000000, wrap_transmutable(AddressRepeater::new(ram.clone(), 32))); + lower_bus.borrow_mut().insert(0x400000, wrap_transmutable(AddressRepeater::new(rom.clone(), 16))); + lower_bus.borrow_mut().insert(0x600000, wrap_transmutable(AddressRepeater::new(rom.clone(), 16))); + } else { + println!("{}: overlay is 1 (startup)", DEV_NAME); + lower_bus.borrow_mut().blocks.clear(); + lower_bus.borrow_mut().insert(0x000000, wrap_transmutable(AddressRepeater::new(rom.clone(), 16))); + lower_bus.borrow_mut().insert(0x200000, wrap_transmutable(AddressRepeater::new(rom.clone(), 16))); + lower_bus.borrow_mut().insert(0x400000, wrap_transmutable(AddressRepeater::new(rom.clone(), 16))); + lower_bus.borrow_mut().insert(0x600000, wrap_transmutable(AddressRepeater::new(ram.clone(), 16))); + } + }); + + mainboard.via.port_a.notify(); + + Ok(mainboard) + } +} + +impl Addressable for Mainboard { + fn len(&self) -> usize { + 0x01000000 + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + if addr < 0x800000 { + self.lower_bus.borrow_mut().read(addr, data) + } else if addr >= 0x900000 && addr < 0xA00000 { + self.scc1.read((addr >> 9) & 0x0F, data) + } else if addr >= 0xB00000 && addr < 0xC00000 { + self.scc1.read((addr >> 9) & 0x0F, data) + } else if addr >= 0xD00000 && addr < 0xE00000 { + self.iwm.read((addr >> 9) & 0x0F, data) + } else if addr >= 0xE80000 && addr < 0xF00000 { + self.via.read((addr >> 9) & 0x0F, data) + } else if addr >= 0xF00000 && addr < 0xF80000 { + // TODO phase read + Ok(()) + } else if addr >= 0xF80000 && addr < 0xF80010 { + // Debugger + Ok(()) + } else { + Err(Error::new(&format!("Error reading address {:#010x}", addr))) + } + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + if addr < 0x800000 { + self.lower_bus.borrow_mut().write(addr, data) + } else if addr >= 0x900000 && addr < 0xA00000 { + self.scc1.write((addr >> 9) & 0x0F, data) + } else if addr >= 0xB00000 && addr < 0xC00000 { + self.scc1.write((addr >> 9) & 0x0F, data) + } else if addr >= 0xD00000 && addr < 0xE00000 { + self.iwm.write((addr >> 9) & 0x0F, data) + } else if addr >= 0xE80000 && addr < 0xF00000 { + self.via.write((addr >> 9) & 0x0F, data) + } else if addr >= 0xF00000 && addr < 0xF80000 { + // TODO phase read + Ok(()) + } else { + Err(Error::new(&format!("Error writing address {:#010x}", addr))) + } + } +} + +impl Steppable for Mainboard { + fn step(&mut self, system: &System) -> Result { + let elapsed = self.via.step(system)?; + + // TODO should this be 1 second, or a multiple of 979_200, which is an 8th of the CPU clock + if self.last_sec + 1_000_000_000 > system.clock { + self.last_sec += 1_000_000_000; + //let port_a = self.via.port_a.borrow_mut(); + // TODO how will the ca1/ca2 cb1/cb2 pins work in the via + system.get_interrupt_controller().set(true, 1, 25)?; + } + Ok(elapsed) + } +} + +impl Transmutable for Mainboard { + fn as_addressable(&mut self) -> Option<&mut dyn Addressable> { + Some(self) + } + + fn as_steppable(&mut self) -> Option<&mut dyn Steppable> { + Some(self) + } +} + + + +pub struct PhaseRead { + +} + +impl PhaseRead { + pub fn new() -> Self { + Self { + } + } +} + +impl Addressable for PhaseRead { + fn len(&self) -> usize { + 0x80000 + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + + Ok(()) + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + + Ok(()) + } +} + + + +pub struct AddressRepeater { + pub subdevice: TransmutableBox, + pub repeat: u8, +} + +impl AddressRepeater { + pub fn new(subdevice: TransmutableBox, repeat: u8) -> Self { + Self { + subdevice, + repeat, + } + } +} + +impl Addressable for AddressRepeater { + fn len(&self) -> usize { + let len = self.subdevice.borrow_mut().as_addressable().unwrap().len(); + len * self.repeat as usize + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + let len = self.subdevice.borrow_mut().as_addressable().unwrap().len() as Address; + self.subdevice.borrow_mut().as_addressable().unwrap().read(addr % len, data) + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + let len = self.subdevice.borrow_mut().as_addressable().unwrap().len() as Address; + self.subdevice.borrow_mut().as_addressable().unwrap().write(addr % len, data) + } +} + +impl Transmutable for AddressRepeater { + fn as_addressable(&mut self) -> Option<&mut dyn Addressable> { + Some(self) + } +} + diff --git a/src/peripherals/macintosh/mod.rs b/src/peripherals/macintosh/mod.rs new file mode 100644 index 0000000..426b19b --- /dev/null +++ b/src/peripherals/macintosh/mod.rs @@ -0,0 +1,5 @@ + +pub mod iwm; +pub mod video; +pub mod mainboard; + diff --git a/src/peripherals/macintosh/video.rs b/src/peripherals/macintosh/video.rs new file mode 100644 index 0000000..da09d98 --- /dev/null +++ b/src/peripherals/macintosh/video.rs @@ -0,0 +1,82 @@ + +use std::sync::{Arc, Mutex}; + +use crate::error::Error; +use crate::system::System; +use crate::devices::{ClockElapsed, Address, Addressable, Steppable, Transmutable}; + +use crate::host::gfx::FrameSwapper; +use crate::host::traits::{Host, BlitableSurface}; + + +const SCRN_BASE: u32 = 0x07A700; + +pub struct MacVideo { + pub swapper: Arc>, +} + +impl MacVideo { + pub fn create(host: &mut H) -> Result { + let swapper = FrameSwapper::new_shared(512, 342); + + host.add_window(FrameSwapper::to_boxed(swapper.clone()))?; + + Ok(Self { + swapper, + }) + } +} + +pub struct BitIter { + pub bit: i8, + pub data: u16, +} + +impl BitIter { + pub fn new(data: u16) -> Self { + Self { + bit: 15, + data, + } + } +} + +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + if self.bit < 0 { + None + } else { + let bit = (self.data & (1 << self.bit)) != 0; + self.bit -= 1; + + if bit { + Some(0xC0C0C0) + } else { + Some(0) + } + } + } +} + +impl Steppable for MacVideo { + fn step(&mut self, system: &System) -> Result { + let mut memory = system.get_bus(); + let mut swapper = self.swapper.lock().unwrap(); + for y in 0..342 { + for x in 0..(512 / 16) { + let word = memory.read_beu16((SCRN_BASE + (x * 2) + (y * (512 / 8))) as Address)?; + swapper.current.blit(x * 16, y, BitIter::new(word), 16, 1); + } + } + Ok(16_600_000) + } +} + +impl Transmutable for MacVideo { + fn as_steppable(&mut self) -> Option<&mut dyn Steppable> { + Some(self) + } +} + diff --git a/src/peripherals/mod.rs b/src/peripherals/mod.rs index 2414147..272b4c6 100644 --- a/src/peripherals/mod.rs +++ b/src/peripherals/mod.rs @@ -1,6 +1,12 @@ pub mod ata; pub mod mc68681; +pub mod mos6522; +pub mod sn76489; +pub mod ym2612; +pub mod z8530; + pub mod genesis; +pub mod macintosh; pub mod trs80; diff --git a/src/peripherals/mos6522.rs b/src/peripherals/mos6522.rs new file mode 100644 index 0000000..80e52df --- /dev/null +++ b/src/peripherals/mos6522.rs @@ -0,0 +1,121 @@ + +use crate::error::Error; +use crate::system::System; +use crate::signals::{Signal, Register}; +use crate::devices::{ClockElapsed, Address, Addressable, Steppable, Transmutable}; + + +const REG_OUTPUT_B: Address = 0x00; +const REG_OUTPUT_A: Address = 0x01; +const REG_DDR_B: Address = 0x02; +const REG_DDR_A: Address = 0x03; +const REG_PERIPH_CTRL: Address = 0x0C; +const REG_INT_FLAGS: Address = 0x0D; +const REG_INT_ENABLE: Address = 0x0E; +const REG_OUTPUT_A_NHS: Address = 0x0F; + + +const DEV_NAME: &'static str = "mos6522"; + + +pub struct Port { + pub data: u8, + pub ddr: u8, +} + +impl Port { + pub fn new() -> Self { + Self { + data: 0xff, + ddr: 0, + } + } +} + + +pub struct Mos6522 { + pub port_a: Register, + pub port_b: Register, + pub peripheral_ctrl: u8, + pub interrupt: Signal, + pub interrupt_flags: u8, + pub interrupt_enable: u8, +} + +impl Mos6522 { + pub fn new() -> Self { + Self { + port_a: Register::new(Port::new()), + port_b: Register::new(Port::new()), + peripheral_ctrl: 0, + interrupt: Signal::new(false), + interrupt_flags: 0, + interrupt_enable: 0, + } + } +} + +impl Addressable for Mos6522 { + fn len(&self) -> usize { + 0x10 + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + match addr { + REG_OUTPUT_B => { data[0] = self.port_b.borrow_mut().data; }, + REG_OUTPUT_A => { data[0] = self.port_a.borrow_mut().data; }, + REG_DDR_B => { data[0] = self.port_b.borrow_mut().ddr; }, + REG_DDR_A => { data[0] = self.port_a.borrow_mut().ddr; }, + REG_INT_FLAGS => { data[0] = self.interrupt_flags; }, + REG_INT_ENABLE => { data[0] = self.interrupt_enable | 0x80; }, + _ => { + warning!("{}: !!! unhandled read from {:0x}", DEV_NAME, addr); + }, + } + debug!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data); + Ok(()) + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]); + match addr { + REG_OUTPUT_B => { self.port_b.borrow_mut().data = data[0]; self.port_b.notify(); }, + REG_OUTPUT_A => { self.port_a.borrow_mut().data = data[0]; self.port_a.notify(); }, + REG_DDR_B => { self.port_b.borrow_mut().ddr = data[0]; self.port_b.notify(); }, + REG_DDR_A => { self.port_a.borrow_mut().ddr = data[0]; self.port_a.notify(); }, + REG_PERIPH_CTRL => { println!("SET TO {:?}", data[0]); self.peripheral_ctrl = data[0]; }, + REG_INT_FLAGS => { self.interrupt_flags &= !data[0] & 0x7F; }, + REG_INT_ENABLE => { + if (data[0] & 0x80) == 0 { + self.interrupt_flags &= !data[0]; + } else { + self.interrupt_flags |= data[0]; + } + }, + REG_OUTPUT_A_NHS => { self.port_a.borrow_mut().data = data[0]; self.port_a.notify(); }, + _ => { + warning!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr); + }, + } + Ok(()) + } +} + +impl Steppable for Mos6522 { + fn step(&mut self, system: &System) -> Result { + + Ok(16_600_000) + } +} + + +impl Transmutable for Mos6522 { + fn as_addressable(&mut self) -> Option<&mut dyn Addressable> { + Some(self) + } + + fn as_steppable(&mut self) -> Option<&mut dyn Steppable> { + Some(self) + } +} + diff --git a/src/peripherals/z8530.rs b/src/peripherals/z8530.rs new file mode 100644 index 0000000..844970c --- /dev/null +++ b/src/peripherals/z8530.rs @@ -0,0 +1,57 @@ + +use crate::error::Error; +use crate::system::System; +use crate::devices::{ClockElapsed, Address, Addressable, Steppable, Transmutable}; + +const DEV_NAME: &'static str = "z8530"; + +pub struct Z8530 { + +} + +impl Z8530 { + pub fn new() -> Self { + Self { + + } + } +} + +impl Addressable for Z8530 { + fn len(&self) -> usize { + 0x10 + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + match addr { + _ => { + warning!("{}: !!! unhandled read from {:0x}", DEV_NAME, addr); + }, + } + debug!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data); + Ok(()) + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]); + match addr { + _ => { + warning!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr); + }, + } + Ok(()) + } +} + + +impl Transmutable for Z8530 { + fn as_addressable(&mut self) -> Option<&mut dyn Addressable> { + Some(self) + } + + //fn as_steppable(&mut self) -> Option<&mut dyn Steppable> { + // Some(self) + //} +} + + diff --git a/src/signals.rs b/src/signals.rs index 9a63395..e6bd23b 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -19,16 +19,28 @@ impl Signal { } } -#[derive(Clone, Debug)] -pub struct Latch(Rc>); +#[derive(Clone)] +//pub struct Register(Rc>); +pub struct Register(Rc>)>>); -impl Latch { - pub fn new(init: T) -> Latch { - Latch(Rc::new(RefCell::new(init))) +impl Register { + pub fn new(init: T) -> Register { + Register(Rc::new(RefCell::new((init, None)))) } pub fn borrow_mut(&self) -> RefMut<'_, T> { - self.0.borrow_mut() + RefMut::map(self.0.borrow_mut(), |v| &mut v.0) + } + + pub fn set_observer(&self, f: F) where F: Fn(&T) + 'static { + self.0.borrow_mut().1 = Some(Box::new(f)); + } + + pub fn notify(&self) { + let data = self.0.borrow(); + if let Some(closure) = &data.1 { + closure(&data.0); + } } } diff --git a/todo.txt b/todo.txt index 0698206..4376358 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,5 @@ +* currently you need to implement the 1.5ms reset in the genesis controllers * should SharedData be HostData, or something else? I don't think the name is very informative * can you make the connections between things (like memory adapters), be expressed in a way that's more similar to the electrical design? like specifying that address pins 10-7 should be ignored/unconnected, pin 11 will connect to "chip select", etc