From e0ef1d8fd9f702deb68a96304dd40436e972fce3 Mon Sep 17 00:00:00 2001 From: transistor Date: Mon, 1 Nov 2021 22:06:40 -0700 Subject: [PATCH] Added start of Z80 decoder --- src/cpus/mod.rs | 1 + src/cpus/z80/decode.rs | 522 ++++++++++++++++++++++++++++++++++++++++ src/cpus/z80/execute.rs | 97 ++++++++ src/cpus/z80/mod.rs | 7 + src/cpus/z80/state.rs | 161 +++++++++++++ todo.txt | 11 - 6 files changed, 788 insertions(+), 11 deletions(-) create mode 100644 src/cpus/z80/decode.rs create mode 100644 src/cpus/z80/execute.rs create mode 100644 src/cpus/z80/mod.rs create mode 100644 src/cpus/z80/state.rs diff --git a/src/cpus/mod.rs b/src/cpus/mod.rs index 58eaa30..b41e53a 100644 --- a/src/cpus/mod.rs +++ b/src/cpus/mod.rs @@ -1,3 +1,4 @@ pub mod m68k; +pub mod z80; diff --git a/src/cpus/z80/decode.rs b/src/cpus/z80/decode.rs new file mode 100644 index 0000000..4ad38e4 --- /dev/null +++ b/src/cpus/z80/decode.rs @@ -0,0 +1,522 @@ + +use crate::error::Error; +use crate::devices::{Address, Addressable}; + +use super::state::{Z80, Z80Type}; + +#[derive(Copy, Clone, Debug)] +pub enum Condition { + NotZero, + Zero, + NotCarry, + Carry, + ParityOdd, + ParityEven, + Positive, + Negative, +} + +#[derive(Copy, Clone, Debug)] +pub enum Register { + A, + B, + C, + D, + E, + H, + L, + F, +} + +#[derive(Copy, Clone, Debug)] +pub enum RegisterPair { + BC, + DE, + HL, + SP, + AF, +} + +#[derive(Copy, Clone, Debug)] +pub enum Target { + DirectRegByte(Register), + IndirectRegByte(RegisterPair), + ImmediateByte(u8), +} + +#[derive(Copy, Clone, Debug)] +pub enum LoadTarget { + DirectRegByte(Register), + DirectRegWord(RegisterPair), + IndirectRegByte(RegisterPair), + IndirectRegWord(RegisterPair), + DirectAltRegByte(Register), + IndirectByte(u16), + IndirectWord(u16), + ImmediateByte(u8), + ImmediateWord(u16), +} + +#[derive(Clone, Debug)] +pub enum Instruction { + ADDa(Target), + ADCa(Target), + ADDhl(RegisterPair), + AND(Target), + CP(Target), + CALL(u16), + CALLcc(Condition, u16), + DEC8(Target), + DEC16(RegisterPair), + DJNZ(i8), + DI, + EI, + EXX, + EXafaf, + EXhlsp, + EXhlde, + INx(u8), + INic(Register), + INC8(Target), + INC16(RegisterPair), + JP(u16), + JPcc(Condition, u16), + JPIndirectHL, + JR(i8), + JRcc(Condition, i8), + LD(LoadTarget, LoadTarget), + NOP, + HALT, + POP(RegisterPair), + PUSH(RegisterPair), + RET, + RETcc(Condition), + RST(u8), + OR(Target), + OUTx(u8), + OUTic(Register), + SUB(Target), + SBCa(Target), + XOR(Target), + + RLC(Target), + RRC(Target), + RL(Target), + RR(Target), + SLA(Target), + SRA(Target), + SLL(Target), + SRL(Target), + BIT(u8, Target), + RES(u8, Target), + SET(u8, Target), + + RLCA, + RRCA, + RLA, + RRA, + DAA, + CPL, + SCF, + CCF, +} + +pub struct Z80Decoder { + pub start: u16, + pub end: u16, + pub instruction: Instruction, +} + +impl Z80Decoder { + pub fn new() -> Self { + Self { + start: 0, + end: 0, + instruction: Instruction::NOP, + } + } +} + +impl Z80Decoder { + pub fn decode_at(&mut self, memory: &mut dyn Addressable, start: u16) -> Result<(), Error> { + self.start = start; + self.end = start; + self.instruction = self.decode_one(memory)?; + Ok(()) + } + + pub fn decode_one(&mut self, memory: &mut dyn Addressable) -> Result { + let ins = self.read_instruction_byte(memory)?; + + match get_ins_x(ins) { + 0 => { + match get_ins_z(ins) { + 0 => { + match get_ins_y(ins) { + 0 => Ok(Instruction::NOP), + 1 => Ok(Instruction::EXafaf), + 2 => { + let offset = self.read_instruction_byte(memory)? as i8; + Ok(Instruction::DJNZ(offset)) + }, + 3 => { + let offset = self.read_instruction_byte(memory)? as i8; + Ok(Instruction::JR(offset)) + }, + y => { + let offset = self.read_instruction_byte(memory)? as i8; + Ok(Instruction::JRcc(get_condition(y - 4), offset)) + }, + } + }, + 1 => { + if get_ins_q(ins) == 0 { + let data = self.read_instruction_word(memory)?; + Ok(Instruction::LD(LoadTarget::DirectRegWord(get_register_pair(get_ins_p(ins))), LoadTarget::ImmediateWord(data))) + } else { + Ok(Instruction::ADDhl(get_register_pair(get_ins_p(ins)))) + } + }, + 2 => { + if (ins & 0x20) == 0 { + let target = match (ins & 0x10) != 0 { + false => LoadTarget::IndirectRegByte(RegisterPair::BC), + true => LoadTarget::IndirectRegByte(RegisterPair::DE), + }; + + match (ins & 0x08) != 0 { + false => Ok(Instruction::LD(target, LoadTarget::DirectRegByte(Register::A))), + true => Ok(Instruction::LD(LoadTarget::DirectRegByte(Register::A), target)), + } + } else { + let addr = self.read_instruction_word(memory)?; + match ((ins >> 3) & 0x03) { + 0 => Ok(Instruction::LD(LoadTarget::IndirectWord(addr), LoadTarget::DirectRegWord(RegisterPair::HL))), + 1 => Ok(Instruction::LD(LoadTarget::IndirectByte(addr), LoadTarget::DirectRegByte(Register::A))), + 2 => Ok(Instruction::LD(LoadTarget::DirectRegWord(RegisterPair::HL), LoadTarget::IndirectWord(addr))), + 3 => Ok(Instruction::LD(LoadTarget::DirectRegByte(Register::A), LoadTarget::IndirectByte(addr))), + _ => panic!("InternalError: impossible value"), + } + } + }, + 3 => { + if get_ins_q(ins) == 0 { + Ok(Instruction::INC16(get_register_pair(get_ins_p(ins)))) + } else { + Ok(Instruction::DEC16(get_register_pair(get_ins_p(ins)))) + } + }, + 4 => { + Ok(Instruction::INC8(get_register(get_ins_y(ins)))) + }, + 5 => { + Ok(Instruction::DEC8(get_register(get_ins_y(ins)))) + }, + 6 => { + let data = self.read_instruction_byte(memory)?; + Ok(Instruction::LD(to_load_target(get_register(get_ins_y(ins))), LoadTarget::ImmediateByte(data))) + }, + 7 => { + match get_ins_y(ins) { + 0 => Ok(Instruction::RLCA), + 1 => Ok(Instruction::RRCA), + 2 => Ok(Instruction::RLA), + 3 => Ok(Instruction::RRA), + 4 => Ok(Instruction::DAA), + 5 => Ok(Instruction::CPL), + 6 => Ok(Instruction::SCF), + 7 => Ok(Instruction::CCF), + _ => panic!("InternalError: impossible value"), + } + }, + _ => panic!("InternalError: impossible value"), + } + }, + 1 => { + if ins == 0x76 { + Ok(Instruction::HALT) + } else { + Ok(Instruction::LD(to_load_target(get_register(get_ins_y(ins))), to_load_target(get_register(get_ins_z(ins))))) + } + }, + 2 => { + Ok(get_alu_instruction(get_ins_y(ins), get_register(get_ins_z(ins)))) + }, + 3 => { + match get_ins_z(ins) { + 0 => { + Ok(Instruction::RETcc(get_condition(get_ins_y(ins)))) + }, + 1 => { + if get_ins_q(ins) == 0 { + Ok(Instruction::POP(get_register_pair_alt(get_ins_p(ins)))) + } else { + match get_ins_p(ins) { + 0 => Ok(Instruction::RET), + 1 => Ok(Instruction::EXX), + 2 => Ok(Instruction::JPIndirectHL), + 3 => Ok(Instruction::LD(LoadTarget::DirectRegWord(RegisterPair::SP), LoadTarget::DirectRegWord(RegisterPair::HL))), + _ => panic!("InternalError: impossible value"), + } + } + }, + 2 => { + let addr = self.read_instruction_word(memory)?; + Ok(Instruction::JPcc(get_condition(get_ins_y(ins)), addr)) + }, + 3 => { + match get_ins_y(ins) { + 0 => { + let addr = self.read_instruction_word(memory)?; + Ok(Instruction::JP(addr)) + }, + 1 => { + self.decode_prefix_cb(memory) + }, + 2 => { + let port = self.read_instruction_byte(memory)?; + Ok(Instruction::OUTx(port)) + }, + 3 => { + let port = self.read_instruction_byte(memory)?; + Ok(Instruction::INx(port)) + }, + 4 => Ok(Instruction::EXhlsp), + 5 => Ok(Instruction::EXhlde), + 6 => Ok(Instruction::DI), + 7 => Ok(Instruction::EI), + _ => panic!("InternalError: impossible value"), + } + }, + 4 => { + let addr = self.read_instruction_word(memory)?; + Ok(Instruction::CALLcc(get_condition(get_ins_y(ins)), addr)) + } + 5 => { + if get_ins_q(ins) == 0 { + Ok(Instruction::PUSH(get_register_pair_alt(get_ins_p(ins)))) + } else { + match get_ins_p(ins) { + 0 => { + let addr = self.read_instruction_word(memory)?; + Ok(Instruction::CALL(addr)) + }, + 1 => self.decode_prefix_dd(memory), + 2 => self.decode_prefix_ed(memory), + 3 => self.decode_prefix_fd(memory), + _ => panic!("Undecoded Instruction"), + } + } + } + 6 => { + let data = self.read_instruction_byte(memory)?; + Ok(get_alu_instruction(get_ins_y(ins), Target::ImmediateByte(data))) + }, + 7 => { + Ok(Instruction::RST(get_ins_y(ins) * 8)) + }, + _ => panic!("InternalError: impossible value"), + } + }, + _ => panic!("Undecoded Instruction"), + } + } + + pub fn decode_prefix_cb(&mut self, memory: &mut dyn Addressable) -> Result { + let ins = self.read_instruction_byte(memory)?; + match get_ins_x(ins) { + 0 => Ok(get_rot_instruction(get_ins_y(ins), get_register(get_ins_z(ins)))), + 1 => Ok(Instruction::BIT(get_ins_y(ins), get_register(get_ins_z(ins)))), + 2 => Ok(Instruction::RES(get_ins_y(ins), get_register(get_ins_z(ins)))), + 3 => Ok(Instruction::SET(get_ins_y(ins), get_register(get_ins_z(ins)))), + _ => panic!("InternalError: impossible value"), + } + } + + pub fn decode_prefix_dd(&mut self, memory: &mut dyn Addressable) -> Result { + panic!("DD instructions unimplemented") + } + + pub fn decode_prefix_ed(&mut self, memory: &mut dyn Addressable) -> Result { + let ins = self.read_instruction_byte(memory)?; + + match get_ins_x(ins) { + 0 => Ok(Instruction::NOP), + 1 => { + /* + match get_ins_z(ins) { + 0 => { + let y = get_ins_y(ins); + if y == 6 { + //Ok(Instruction::INic), + panic!(""); + } else { + //Ok(Instruction::INic(get_register(y))), + } + }, + 1 => { + }, + 2 => { + }, + 3 => { + }, + 4 => { + }, + 5 => { + }, + 6 => { + }, + 7 => { + }, + _ => panic!("InternalError: impossible value"), + } + */ + panic!("random instructions are unimplemented"); + }, + 2 => { + match ins & 0xF0 { + // TODO implement block + //0xA0 => { + + //}, + //0xB0 => { + + //}, + _ => Ok(Instruction::NOP), + } + }, + 3 => Ok(Instruction::NOP), + _ => panic!("InternalError: impossible value"), + } + } + + pub fn decode_prefix_fd(&mut self, memory: &mut dyn Addressable) -> Result { + panic!("FD instructions unimplemented") + } + + + fn read_instruction_byte(&mut self, device: &mut dyn Addressable) -> Result { + let byte = device.read_u8(self.end as Address)?; + self.end += 1; + Ok(byte) + } + + fn read_instruction_word(&mut self, device: &mut dyn Addressable) -> Result { + let word = device.read_beu16(self.end as Address)?; + self.end += 2; + Ok(word) + } + + pub fn dump_decoded(&mut self, memory: &mut dyn Addressable) { + let ins_data: Result = + (0..((self.end - self.start) / 2)).map(|offset| + Ok(format!("{:02x} ", memory.read_u8((self.start + offset) as Address).unwrap())) + ).collect(); + println!("{:#06x}: {}\n\t{:?}\n", self.start, ins_data.unwrap(), self.instruction); + } +} + +fn get_alu_instruction(alu: u8, target: Target) -> Instruction { + match alu { + 0 => Instruction::ADDa(target), + 1 => Instruction::ADCa(target), + 2 => Instruction::SUB(target), + 3 => Instruction::SBCa(target), + 4 => Instruction::AND(target), + 5 => Instruction::XOR(target), + 6 => Instruction::OR(target), + 7 => Instruction::CP(target), + _ => panic!("InternalError: impossible value"), + } +} + +fn get_rot_instruction(rot: u8, target: Target) -> Instruction { + match rot { + 0 => Instruction::RLC(target), + 1 => Instruction::RRC(target), + 2 => Instruction::RL(target), + 3 => Instruction::RR(target), + 4 => Instruction::SLA(target), + 5 => Instruction::SRA(target), + 6 => Instruction::SLL(target), + 7 => Instruction::SRL(target), + _ => panic!("InternalError: impossible value"), + } +} + +fn get_register(reg: u8) -> Target { + match reg { + 0 => Target::DirectRegByte(Register::B), + 1 => Target::DirectRegByte(Register::C), + 2 => Target::DirectRegByte(Register::D), + 3 => Target::DirectRegByte(Register::E), + 4 => Target::DirectRegByte(Register::H), + 5 => Target::DirectRegByte(Register::L), + 6 => Target::IndirectRegByte(RegisterPair::HL), + 7 => Target::DirectRegByte(Register::A), + _ => panic!("InternalError: impossible value"), + } +} + +fn to_load_target(target: Target) -> LoadTarget { + match target { + Target::DirectRegByte(reg) => LoadTarget::DirectRegByte(reg), + Target::IndirectRegByte(reg) => LoadTarget::IndirectRegByte(reg), + Target::ImmediateByte(data) => LoadTarget::ImmediateByte(data), + } +} + +fn get_register_pair(reg: u8) -> RegisterPair { + match reg { + 0 => RegisterPair::BC, + 1 => RegisterPair::DE, + 2 => RegisterPair::HL, + 3 => RegisterPair::SP, + _ => panic!("InternalError: impossible value"), + } +} + +fn get_register_pair_alt(reg: u8) -> RegisterPair { + match reg { + 0 => RegisterPair::BC, + 1 => RegisterPair::DE, + 2 => RegisterPair::HL, + 3 => RegisterPair::AF, + _ => panic!("InternalError: impossible value"), + } +} + +fn get_condition(cond: u8) -> Condition { + match cond { + 0 => Condition::NotZero, + 1 => Condition::Zero, + 2 => Condition::NotCarry, + 3 => Condition::Carry, + 4 => Condition::ParityOdd, + 5 => Condition::ParityEven, + 6 => Condition::Positive, + 7 => Condition::Negative, + _ => panic!("InternalError: impossible value"), + } +} + +fn get_ins_x(ins: u8) -> u8 { + (ins >> 6) & 0x03 +} + +fn get_ins_y(ins: u8) -> u8 { + (ins >> 3) & 0x07 +} + +fn get_ins_z(ins: u8) -> u8 { + ins & 0x07 +} + +fn get_ins_p(ins: u8) -> u8 { + (ins >> 4) & 0x03 +} + +fn get_ins_q(ins: u8) -> u8 { + (ins >> 3) & 0x01 +} + diff --git a/src/cpus/z80/execute.rs b/src/cpus/z80/execute.rs new file mode 100644 index 0000000..a681d29 --- /dev/null +++ b/src/cpus/z80/execute.rs @@ -0,0 +1,97 @@ + +use crate::system::System; +use crate::error::{ErrorType, Error}; +use crate::devices::{ClockElapsed, Address, Steppable, Interruptable, Addressable, Debuggable, Transmutable}; + +use super::decode::Z80Decoder; +use super::state::{Z80, Z80State, Status}; + +impl Steppable for Z80 { + fn step(&mut self, system: &System) -> Result { + self.step_internal(system)?; + Ok((1_000_000_000 / self.frequency as u64) * 4) + } + + fn on_error(&mut self, system: &System) { + //self.dump_state(system); + } +} + +impl Interruptable for Z80 { } + +impl Transmutable for Z80 { + fn as_steppable(&mut self) -> Option<&mut dyn Steppable> { + Some(self) + } + + fn as_interruptable(&mut self) -> Option<&mut dyn Interruptable> { + Some(self) + } + + //fn as_debuggable(&mut self) -> Option<&mut dyn Debuggable> { + // Some(self) + //} +} + + + +impl Z80 { + pub fn step_internal(&mut self, system: &System) -> Result<(), Error> { + match self.state.status { + Status::Init => self.init(system), + Status::Halted => Err(Error::new("CPU stopped")), + Status::Running => { + match self.cycle_one(system) { + Ok(()) => Ok(()), + //Err(Error { err: ErrorType::Processor, native, .. }) => { + Err(Error { err: ErrorType::Processor, native, .. }) => { + //self.exception(system, native as u8, false)?; + Ok(()) + }, + Err(err) => Err(err), + } + }, + } + } + + pub fn init(&mut self, system: &System) -> Result<(), Error> { + //self.state.msp = self.port.read_beu32(0)?; + //self.state.pc = self.port.read_beu32(4)?; + self.state.status = Status::Running; + Ok(()) + } + + pub fn cycle_one(&mut self, system: &System) -> Result<(), Error> { + //self.timer.cycle.start(); + self.decode_next(system)?; + //self.execute_current(system)?; + //self.timer.cycle.end(); + + //if (self.timer.cycle.events % 500) == 0 { + // println!("{}", self.timer); + //} + + //self.check_pending_interrupts(system)?; + Ok(()) + } + + pub fn decode_next(&mut self, system: &System) -> Result<(), Error> { + //self.check_breakpoints(system); + + //self.timer.decode.start(); + self.decoder.decode_at(&mut self.port, self.state.pc)?; + //self.timer.decode.end(); + + //if self.debugger.use_tracing { + self.decoder.dump_decoded(&mut self.port); + //} + + self.state.pc = self.decoder.end; + Ok(()) + } + + pub fn execute_current(&mut self, system: &System) -> Result<(), Error> { + panic!("unimplemented"); + } +} + diff --git a/src/cpus/z80/mod.rs b/src/cpus/z80/mod.rs new file mode 100644 index 0000000..b45e8b6 --- /dev/null +++ b/src/cpus/z80/mod.rs @@ -0,0 +1,7 @@ + +pub mod state; +pub mod decode; +pub mod execute; + +pub use self::state::{Z80, Z80Type}; + diff --git a/src/cpus/z80/state.rs b/src/cpus/z80/state.rs new file mode 100644 index 0000000..e6ae3b1 --- /dev/null +++ b/src/cpus/z80/state.rs @@ -0,0 +1,161 @@ + +use crate::system::System; +use crate::devices::Address; +use crate::memory::BusPort; + +use super::decode::Z80Decoder; +//use super::debugger::M68kDebugger; + + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Z80Type { + Z80, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Status { + Init, + Running, + Halted, +} + + +#[repr(u8)] +#[allow(dead_code)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Flags { + Carry = 0x0001, + Overflow = 0x0002, + Zero = 0x0004, + Negative = 0x0008, + Extend = 0x0010, +} + +/* +#[repr(u8)] +#[allow(dead_code)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Exceptions { + BusError = 2, + AddressError = 3, + IllegalInstruction = 4, + ZeroDivide = 5, + ChkInstruction = 6, + TrapvInstruction = 7, + PrivilegeViolation = 8, +} + + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum InterruptPriority { + NoInterrupt = 0, + Level1 = 1, + Level2 = 2, + Level3 = 3, + Level4 = 4, + Level5 = 5, + Level6 = 6, + Level7 = 7, +} + +impl InterruptPriority { + pub fn from_u8(priority: u8) -> InterruptPriority { + match priority { + 0 => InterruptPriority::NoInterrupt, + 1 => InterruptPriority::Level1, + 2 => InterruptPriority::Level2, + 3 => InterruptPriority::Level3, + 4 => InterruptPriority::Level4, + 5 => InterruptPriority::Level5, + 6 => InterruptPriority::Level6, + _ => InterruptPriority::Level7, + } + } +} +*/ + +#[derive(Clone, Debug, PartialEq)] +pub struct Z80State { + pub status: Status, + + pub pc: u16, + pub sp: u16, + pub ix: u16, + pub iy: u16, + + pub reg: [u8; 8], + pub alt_reg: [u8; 8], + + pub i: u8, + pub r: u8, +} + +impl Z80State { + pub fn new() -> Self { + Self { + status: Status::Init, + + pc: 0, + sp: 0, + ix: 0, + iy: 0, + + reg: [0; 8], + alt_reg: [0; 8], + + i: 0, + r: 0, + } + } +} + +pub struct Z80 { + pub cputype: Z80Type, + pub frequency: u32, + pub state: Z80State, + pub decoder: Z80Decoder, + //pub debugger: M68kDebugger, + pub port: BusPort, +} + +impl Z80 { + pub fn new(cputype: Z80Type, frequency: u32, port: BusPort) -> Self { + Self { + cputype, + frequency, + state: Z80State::new(), + decoder: Z80Decoder::new(), + //debugger: M68kDebugger::new(), + port: port, + } + } + + #[allow(dead_code)] + pub fn reset(&mut self) { + self.state = Z80State::new(); + //self.decoder = M68kDecoder::new(self.cputype, 0); + //self.debugger = M68kDebugger::new(); + } + + /* + pub fn dump_state(&mut self, system: &System) { + println!("Status: {:?}", self.state.status); + println!("PC: {:#010x}", self.state.pc); + println!("SR: {:#06x}", self.state.sr); + for i in 0..7 { + println!("D{}: {:#010x} A{}: {:#010x}", i, self.state.d_reg[i as usize], i, self.state.a_reg[i as usize]); + } + println!("D7: {:#010x}", self.state.d_reg[7]); + println!("MSP: {:#010x}", self.state.msp); + println!("USP: {:#010x}", self.state.usp); + + println!("Current Instruction: {:#010x} {:?}", self.decoder.start, self.decoder.instruction); + println!(""); + self.port.dump_memory(self.state.msp as Address, 0x40); + println!(""); + } + */ +} + diff --git a/todo.txt b/todo.txt index 755a7d8..72d3c4b 100644 --- a/todo.txt +++ b/todo.txt @@ -2,11 +2,6 @@ * 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) - -* sort out how inputing keys will work -* separate the debugger out of m68k, use on_debug or something to print out debug info -* make devices nameable, using a hashmap to store them - * 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 * should you modify Addressable to also take the absolute address as input? I'm thinking of how the same device could be mapped to multiple addresses in memory instead @@ -15,12 +10,6 @@ So both could share the same Signal, one setting it and the other reading it, but how would you actually configure/build that? -We Need: - * devices that change address mapping during operation - * device that can be mapped to multiple separated locations while sharing data - * subdevices that can share data (eg. signal from one to another (mac128 via outputs), or being a subdevice with a special trait (joystick)) - - * implement a Z80 * maybe see about a Mac 128k or something