Add basic hardware IO.

Emulator passes bios! hooray
This commit is contained in:
Kevin Hamacher 2016-05-26 14:18:02 +02:00
parent 8df38e9c11
commit 94e73b2eda
6 changed files with 536 additions and 43 deletions

View File

@ -6,7 +6,7 @@ E000 FCFF Copy of the work RAM
FE00 FE9F OAM (Sprite Attribute Table)
FEA0 FEFF Unused
FF00 FF7F Hardware IO
FE80 FFFE High RAM
FF80 FFFE High RAM
FFFF FFFF Interrupt switch
Hardware IO:

View File

@ -32,6 +32,8 @@ fn to_u16(bytes: Box<[u8]>) -> u16 {
impl CPU {
pub fn new(interconnect: interconnect::Interconnect) -> CPU {
// Ugly patch ^.^
// interconnect.write_byte(0xFF44 as u16, 0x90);
CPU {
flags: 0,
regs: [0, 0, 0, 0, 0, 0, 0],
@ -59,7 +61,8 @@ impl CPU {
if reg_id == 7 {
self.regs[REG_A] = value;
} else if reg_id == 6 {
panic!("(HL) not supported yet.");
let addr: u16 = self.get_pair_value(REG_H, REG_L);
self.interconnect.write_byte(addr, value);
} else {
self.regs[reg_id] = value;
}
@ -70,7 +73,8 @@ impl CPU {
if reg_id == 7 {
self.regs[REG_A]
} else if reg_id == 6 {
panic!("(HL) not supported yet.");
let addr: u16 = self.get_pair_value(REG_H, REG_L);
self.interconnect.read_byte(addr)
} else {
self.regs[reg_id]
}
@ -83,10 +87,75 @@ impl CPU {
match instruction {
0x10 ... 0x17 => {
let reg_id = (instruction - 0x10) as usize;
let args = self.load_args(1);
let mut val = self.get_8bit_reg(reg_id);
println!("RL {}, {}", REG_NAMES[reg_id], args[0]);
self.set_8bit_reg(reg_id, val.rotate_left(args[0] as u32));
let val = self.get_8bit_reg(reg_id);
println!("RL {}", REG_NAMES[reg_id]);
self.regs[REG_A].rotate_left(val as u32);
}
0x40 ... 0x47 => {
// Test 0th bit
let reg_id = (instruction - 0x40) as usize;
let reg_content = self.get_8bit_reg(reg_id);
println!("BIT 0, {}", REG_NAMES[reg_id]);
if reg_content & (1 << 0) == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
}
0x48 ... 0x4F => {
// Test 1th bit
let reg_id = (instruction - 0x48) as usize;
let reg_content = self.get_8bit_reg(reg_id);
println!("BIT 1, {}", REG_NAMES[reg_id]);
if reg_content & (1 << 1) == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
}
0x50 ... 0x57 => {
// Test 2th bit
let reg_id = (instruction - 0x50) as usize;
let reg_content = self.get_8bit_reg(reg_id);
println!("BIT 2, {}", REG_NAMES[reg_id]);
if reg_content & (1 << 2) == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
}
0x58 ... 0x5F => {
// Test 3th bit
let reg_id = (instruction - 0x58) as usize;
let reg_content = self.get_8bit_reg(reg_id);
println!("BIT 3, {}", REG_NAMES[reg_id]);
if reg_content & (1 << 3) == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
}
0x60 ... 0x67 => {
// Test 4th bit
let reg_id = (instruction - 0x60) as usize;
let reg_content = self.get_8bit_reg(reg_id);
println!("BIT 4, {}", REG_NAMES[reg_id]);
if reg_content & (1 << 4) == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
}
0x68 ... 0x6F => {
// Test 5th bit
let reg_id = (instruction - 0x68) as usize;
let reg_content = self.get_8bit_reg(reg_id);
println!("BIT 5, {}", REG_NAMES[reg_id]);
if reg_content & (1 << 5) == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
}
0x70 ... 0x77 => {
// Test 6th bit
@ -117,51 +186,151 @@ impl CPU {
}
fn get_pair_value(&self, a: usize, b: usize) -> u16 {
(self.regs[a] as u16) | (self.regs[b] as u16) << 8
(self.regs[a] as u16) << 8 | (self.regs[b] as u16)
}
fn set_pair_value(&mut self, a: usize, b: usize, value: u16) {
self.regs[a] = value as u8;
self.regs[b] = (value >> 8) as u8;
self.regs[a] = (value >> 8) as u8;
self.regs[b] = value as u8;
}
fn dump_stack(&self) {
for i in self.sp .. 0xFFFE {
println!("[{:#04X}]: {:#02X}", i, self.interconnect.read_byte(i));
}
}
fn reg_inc(&mut self, reg_id: usize) {
self.regs[reg_id] = self.regs[reg_id].wrapping_add(1);
if self.regs[reg_id] == 0 {
self.flags |= FLAG_Z;
}
self.flags &= !FLAG_N;
}
fn reg_dec(&mut self, reg_id: usize) {
self.regs[reg_id] = self.regs[reg_id].wrapping_sub(1);
if self.regs[reg_id] == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
self.flags |= FLAG_N;
}
pub fn run_instruction(&mut self) {
let instruction = self.read_byte(self.ip);
print!("{:#04x}: {:#04x}: ", &self.ip, &instruction);
print!("{:#04x}: [SP: {:#04X}] i={:02X}. ", &self.ip, &self.sp, &instruction);
for i in 0 .. 6 {
print!("{}: {:02X} ", REG_NAMES[i], self.get_8bit_reg(i));
}
print!("A: {:02X} ", self.regs[REG_A]);
print!("I: {:02X} ", self.interconnect.read_byte(0xFFFF));
// Flags
print!("Z={} ", self.flags & FLAG_Z != 0);
print!("N={} ", self.flags & FLAG_N != 0);
print!("H={} ", self.flags & FLAG_H != 0);
print!("C={} ", self.flags & FLAG_C != 0);
self.ip += 1;
match instruction {
0x04 => {
println!("INC B");
self.reg_inc(REG_B);
},
0x05 => {
println!("DEC B");
self.regs[REG_B].wrapping_sub(1);
}
self.reg_dec(REG_B);
},
0x06 => {
let args = self.load_args(1);
println!("LD B, {:02x}", args[0]);
self.regs[REG_B] = args[0];
}
},
0x0C => {
println!("INC C");
self.regs[REG_C] += 1;
}
self.reg_inc(REG_C);
},
0x0D => {
println!("DEC C");
self.reg_dec(REG_C);
},
0x0E => {
let args = self.load_args(1);
println!("LD C, {:02x}", args[0]);
self.regs[REG_C] = args[0];
}
},
0x11 => {
let args = self.load_args(2);
println!("LD DE, {:02x}{:02x}", args[1], args[0]);
let val = to_u16(args);
println!("LD DE, {:04x}", val);
self.set_pair_value(REG_D, REG_E, val);
},
0x13 => {
println!("INC DE");
let old_value = self.get_pair_value(REG_D, REG_E);
self.set_pair_value(REG_D, REG_E, old_value + 1);
},
0x14 => {
println!("INC D");
self.reg_inc(REG_D);
},
0x15 => {
println!("DEC D");
self.reg_dec(REG_D);
},
0x16 => {
let args = self.load_args(1);
println!("LD D, {:02x}", args[0]);
self.regs[REG_D] = args[0];
self.regs[REG_E] = args[1];
},
0x17 => {
println!("RLA");
let carry = self.flags & FLAG_C == FLAG_C;
if !carry {
// No carry before, now we got a carry => set it
if self.regs[REG_A] & 0x80 == 0x80 {
self.flags |= FLAG_C
}
self.regs[REG_A] = self.regs[REG_A] << 1;
} else {
if self.regs[REG_A] & 0x80 == 0 {
self.flags &= !FLAG_C;
}
self.regs[REG_A] = self.regs[REG_A] << 1 | 1;
}
},
0x18 => {
let args = self.load_args(1);
println!("JR {:02X}", args[0] as i8);
let off: i8 = args[0] as i8;
if off < 0 {
self.ip -= (-off) as u16;
} else {
self.ip += off as u16;
}
},
0x1A => {
println!("LD A, (DE)");
self.regs[REG_A] = self.interconnect.read_byte(self.get_pair_value(REG_D, REG_E));
}
},
0x1C => {
println!("INC E");
self.reg_inc(REG_E);
},
0x1D => {
println!("DEC E");
self.reg_dec(REG_E);
},
0x1E => {
let args = self.load_args(1);
println!("LD E, {:02x}", args[0]);
self.regs[REG_E] = args[0];
},
0x20 => {
let args = self.load_args(1);
println!("JR NZ {:02x}", args[0]);
println!("JR NZ {:02x}", args[0] as i8);
if self.flags & FLAG_Z == 0 {
let offset = args[0] as i8;
if offset < 0 {
@ -172,16 +341,58 @@ impl CPU {
}
}
0x21 => {
let args = self.load_args(2);
println!("LD HL, {:02x}{:02x}", args[1], args[0]);
self.regs[REG_H] = args[0];
self.regs[REG_L] = args[1];
let value = to_u16(self.load_args(2));
println!("LD HL, {:04x}", value);
self.set_pair_value(REG_H, REG_L, value);
},
0x22 => {
println!("LD (HL+), A");
let addr: u16 = self.get_pair_value(REG_H, REG_L);
self.interconnect.write_byte(addr, self.regs[REG_A]);
self.set_pair_value(REG_H, REG_L, addr + 1);
},
0x23 => {
println!("INC HL");
let old_value = self.get_pair_value(REG_H, REG_L);
self.set_pair_value(REG_H, REG_L, old_value + 1);
},
0x24 => {
println!("INC H");
self.reg_inc(REG_H);
},
0x25 => {
println!("DEC H");
self.reg_dec(REG_H);
},
0x26 => {
let args = self.load_args(1);
println!("LD H, {:02x}", args[0]);
self.regs[REG_H] = args[0];
},
0x28 => {
let args = self.load_args(1);
let mut target;
if args[0] < 0 {
target = self.ip - (-(args[0] as i8)) as u16;
} else {
target = self.ip + args[0] as u16;
}
println!("LR Z, {:04X}", target);
self.ip = target;
},
0x2C => {
println!("INC L");
self.reg_inc(REG_L);
},
0x2D => {
println!("DEC L");
self.reg_dec(REG_L);
},
0x2E => {
let args = self.load_args(1);
println!("LD L, {:02x}", args[0]);
self.regs[REG_L] = args[0];
},
0x31 => {
let args = self.load_args(2);
self.sp = to_u16(args);
@ -189,18 +400,33 @@ impl CPU {
},
0x32 => {
println!("LD (HL-), A");
let mut addr: u16 = (self.regs[REG_H] as u16) | ((self.regs[REG_L] as u16) << 8);
let mut addr = self.get_pair_value(REG_H, REG_L);
self.interconnect.write_byte(addr, self.regs[REG_A]);
addr -= 1;
self.regs[REG_H] = addr as u8;
self.regs[REG_L] = (addr >> 8) as u8;
self.set_pair_value(REG_H, REG_L, addr);
},
0x36 => {
let args = self.load_args(1);
println!("LD (HL), {:02x}", args[0]);
let addr = self.get_pair_value(REG_H, REG_L);
self.interconnect.write_byte(addr, args[0]);
},
0x3C => {
println!("INC A");
self.reg_inc(REG_A);
}
0x3D => {
println!("DEC A");
self.reg_dec(REG_A);
}
0x3E => {
let args = self.load_args(1);
println!("LD A, {:02x}", args[0]);
self.regs[REG_A] = args[0];
},
// LDs
0x40 ... 0x47 => {
let reg_id = (instruction - 0x40) as usize;
println!("LD B, {}", REG_NAMES[reg_id]);
@ -235,7 +461,7 @@ impl CPU {
let reg_id = (instruction - 0x70) as usize;
println!("LD (HL), {}", REG_NAMES[reg_id]);
let reg_value: u8 = self.get_8bit_reg(reg_id);
let addr: u16 = (self.regs[REG_L] as u16) << 8 | (self.regs[REG_H] as u16);
let addr: u16 = self.get_pair_value(REG_H, REG_L);
self.interconnect.write_byte(addr, reg_value);
},
@ -244,10 +470,81 @@ impl CPU {
println!("LD A, {}", REG_NAMES[reg_id]);
self.regs[REG_A] = self.get_8bit_reg(reg_id);
},
// ADD
0x80 ... 0x87 => {
let reg_id = (instruction - 0x80) as usize;
println!("ADD {}", REG_NAMES[reg_id]);
self.regs[REG_A] = self.regs[REG_A].wrapping_add(self.get_8bit_reg(reg_id));
self.flags &= !FLAG_N;
}
// ADC
0x88 ... 0x8F => {
let reg_id = (instruction - 0x88) as usize;
println!("ADC {}", REG_NAMES[reg_id]);
self.regs[REG_A] = self.regs[REG_A].wrapping_add(self.get_8bit_reg(reg_id));
if self.flags & FLAG_C == FLAG_C {
self.regs[REG_A] = self.regs[REG_A].wrapping_add(1);
}
self.flags &= !FLAG_N;
}
// SUBs
0x90 ... 0x97 => {
let reg_id = (instruction - 0x90) as usize;
println!("SUB {}", REG_NAMES[reg_id]);
self.regs[REG_A] = self.regs[REG_A].wrapping_sub(self.get_8bit_reg(reg_id));
self.flags |= FLAG_N;
if self.regs[REG_A] == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
// TODO: H, C
}
// SBC
0x98 ... 0x9F => {
let reg_id = (instruction - 0x98) as usize;
println!("SBC {}", REG_NAMES[reg_id]);
self.regs[REG_A] = self.regs[REG_A].wrapping_sub(self.get_8bit_reg(reg_id));
if self.flags & FLAG_C == FLAG_C {
self.regs[REG_A] = self.regs[REG_A].wrapping_sub(1);
}
self.flags |= FLAG_N;
}
// XOR
0xA8 ... 0xAF => {
let reg_id = (instruction - 0xA8) as usize;
println!("XOR {}", REG_NAMES[reg_id]);
self.regs[REG_A] ^= self.get_8bit_reg(reg_id);
if self.regs[REG_A] == 0 {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
self.flags &= !FLAG_C;
self.flags &= !FLAG_N;
self.flags &= !FLAG_H;
},
// CP
0xB8 ... 0xBF => {
let reg_id = (instruction - 0xB8) as usize;
println!("CP {}", REG_NAMES[reg_id]);
let val = self.get_8bit_reg(reg_id);
if self.regs[REG_A] < self.get_8bit_reg(reg_id) {
self.flags |= FLAG_C;
self.flags &= !FLAG_Z;
} else if self.regs[REG_A] == self.get_8bit_reg(reg_id) {
self.flags |= FLAG_Z;
self.flags &= !FLAG_C;
} else {
self.flags &= !FLAG_Z;
self.flags &= !FLAG_C;
}
},
0xC1 => {
println!("POP BC");
@ -258,9 +555,15 @@ impl CPU {
0xC5 => {
println!("PUSH BC");
let val: u16 = self.get_pair_value(REG_B, REG_C);
self.interconnect.write_word(self.sp, val);
self.interconnect.write_word(self.sp - 2, val);
self.sp -= 2;
},
0xC9 => {
println!("RET");
self.dump_stack();
self.ip = self.interconnect.read_word(self.sp+1);
self.sp += 2;
},
0xCB => {
// Prefix CB. This is annoying.
self.run_prefix_instruction();
@ -273,23 +576,56 @@ impl CPU {
// self.interconnect.write_byte(self.sp - 1, (self.ip >> 8) as u8);
self.interconnect.write_word(self.sp - 1, self.ip);
self.sp -= 2;
self.dump_stack();
self.ip = target;
}
},
0xE0 => {
let args = self.load_args(1);
println!("LDH {:02X}, A", args[0]);
self.interconnect.write_byte(0xFF00 + args[0] as u16, self.regs[REG_A]);
}
},
0xE2 => {
println!("LD (C), A");
println!("[{:04X}] = {:02X}", 0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]);
self.interconnect.write_byte(0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A])
},
0xEA => {
let addr = to_u16(self.load_args(2));
println!("LD ({:04X}), A", addr);
self.interconnect.write_byte(addr, self.regs[REG_A]);
}
0xF0 => {
let args = self.load_args(1);
println!("LDH A, {:02X}", args[0]);
self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + args[0] as u16);
},
0xFB => {
// Enable interrupts - TODO
println!("EI");
self.interconnect.write_byte(0xFFFF, 0x01);
panic!("Uh uh");
},
0xFE => {
let args = self.load_args(1);
println!("CP {:02X}", args[0]);
self.flags |= FLAG_N;
if args[0] > self.regs[REG_A] {
self.flags |= FLAG_C;
} else {
self.flags &= !FLAG_C;
}
if args[0] == self.regs[REG_A] {
self.flags |= FLAG_Z;
} else {
self.flags &= !FLAG_Z;
}
// TODO H
}
_ => panic!("Unknown instruction: {:02x}", instruction)
}
// self.dump_stack();
self.interconnect.tick();
}
}

51
src/display.rs Normal file
View File

@ -0,0 +1,51 @@
#[derive(Debug, Default)]
pub struct Display {
control: u8,
status: u8,
background_palette: u8,
scrollx: u8,
scrolly: u8,
curline: u8,
// TODO
}
impl Display {
pub fn new() -> Display {
Display::default()
}
pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0xFF40 => self.control = val,
0xFF41 => self.status = val,
0xFF42 => self.scrolly = val,
0xFF43 => self.scrollx = val,
0xFF44 => self.curline = 0,
0xFF47 => self.background_palette = val,
_ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr),
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
match addr {
0xFF40 => self.control,
0xFF41 => self.status,
0xFF42 => self.scrolly,
0xFF43 => self.scrollx,
0xFF44 => self.curline,
0xFF47 => self.background_palette,
_ => panic!("Display: Read from {:04X} unsupported", addr),
}
}
pub fn vblank_interrupt(&mut self) {
self.curline += 1;
if self.curline > 153 {
self.curline = 0;
}
}
pub fn controller_interrupt(&self) {
}
}

View File

@ -1,13 +1,24 @@
const RAM_SIZE: usize = 0x2000;
const VRAM_SIZE: usize = 0x2000;
const HIRAM_SIZE: usize = (0xFFFE - 0xFE80) + 1;
const HIRAM_SIZE: usize = (0xFFFE - 0xFF80) + 1;
const INTERRUPT_DISPLAY: u8 = 1 << 1;
const INTERRUPT_DISPLAY_VBLANK: u8 = 1 << 0;
use super::display;
use super::sound;
pub struct Interconnect {
bios: Box<[u8]>,
rom: Box<[u8]>,
ram: Box<[u8]>,
vram: Box<[u8]>,
hiram: Box<[u8]>
hiram: Box<[u8]>,
sound: sound::Sound,
display: display::Display,
interrupt: u8,
timer: u8,
disable_bootrom: u8,
}
impl Interconnect {
@ -18,6 +29,32 @@ impl Interconnect {
ram: vec![0; RAM_SIZE].into_boxed_slice(),
vram: vec![0; VRAM_SIZE].into_boxed_slice(),
hiram: vec![0; HIRAM_SIZE].into_boxed_slice(),
sound: sound::Sound::new(),
display: display::Display::new(),
interrupt: 0,
timer: 0,
disable_bootrom: 0,
}
}
// Somehow we need different timers for this.
pub fn tick(&mut self) {
self.timer += 1;
if self.timer == 5 {
self.display_blank_interrupt();
self.timer = 0;
}
}
pub fn display_blank_interrupt(&mut self) {
if self.interrupt & INTERRUPT_DISPLAY_VBLANK >= 0 {
self.display.vblank_interrupt();
}
}
pub fn display_interrupt(&mut self) {
if self.interrupt & INTERRUPT_DISPLAY > 0 {
self.display.controller_interrupt();
}
}
@ -28,7 +65,7 @@ impl Interconnect {
match addr {
0x0000 ... 0x7FFF => {
// TODO: Check if bios or cartridge (additional condition: isEnabled)
if addr < 0x100 {
if addr < 0x100 && self.disable_bootrom == 0 {
self.bios[addr as usize]
} else {
self.rom[addr as usize]
@ -40,8 +77,20 @@ impl Interconnect {
0xC000 ... 0xDFFF => {
self.ram[(addr - 0xC000) as usize]
},
0xFE80 ... 0xFFFE => {
self.hiram[(addr - 0xFE80) as usize]
0xFF10 ... 0xFF26 => {
self.sound.read_byte(addr)
},
0xFF40 ... 0xFF4B => {
self.display.read_byte(addr)
},
0xFF50 => {
self.disable_bootrom
},
0xFF80 ... 0xFFFE => {
self.hiram[(addr - 0xFF80) as usize]
},
0xFFFF => {
self.interrupt
}
_ => {
panic!("Read from {:04X} not supported.", addr);
@ -61,7 +110,7 @@ impl Interconnect {
FE00 FE9F OAM (Sprite Attribute Table)
FEA0 FEFF Unused
FF00 FF7F Hardware IO
FE80 FFFE High RAM
FF80 FFFE High RAM
FFFF FFFF Interrupt switch
*/
@ -72,9 +121,21 @@ impl Interconnect {
0xC000 ... 0xDFFF => {
self.ram[(addr - 0xC000) as usize] = val;
},
0xFE80 ... 0xFFFE => {
self.hiram[(addr - 0xFE80) as usize] = val;
0xFF10 ... 0xFF26 => {
self.sound.write_byte(addr, val);
},
0xFF40 ... 0xFF4B => {
self.display.write_byte(addr, val);
},
0xFF50 => {
self.disable_bootrom = val;
}
0xFF80 ... 0xFFFE => {
self.hiram[(addr - 0xFF80) as usize] = val;
},
0xFFFF => {
self.interrupt = val;
},
_ => {
panic!("Write {:02X} to {:04X} not supported.", val, addr);
}

View File

@ -7,7 +7,10 @@ use std::fs;
use std::env;
mod cpu;
mod display;
mod interconnect;
mod sound;
fn main() {
let bios_path = env::args().nth(1).unwrap();
@ -16,11 +19,11 @@ fn main() {
let rom = read_rom(&rom_path);
// Now we need to execute commands
let mut interconnect = interconnect::Interconnect::new(bios, rom);
let mut CPU = cpu::CPU::new(interconnect);
let interconnect = interconnect::Interconnect::new(bios, rom);
let mut cpu = cpu::CPU::new(interconnect);
loop {
CPU.run_instruction();
cpu.run_instruction();
}
}

42
src/sound.rs Normal file
View File

@ -0,0 +1,42 @@
#[derive(Debug, Default)]
pub struct Sound {
enabled: u8,
sound_length_1: u8,
sound_control_1: u8,
sound_channel_volume_control: u8,
sound_output_terminal_selector: u8,
sound_freq_low: u8,
sound_freq_high: u8,
}
impl Sound {
pub fn new() -> Sound {
Sound::default()
}
pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0xFF11 => self.sound_length_1 = val,
0xFF12 => self.sound_control_1 = val,
0xFF13 => self.sound_freq_low = val,
0xFF14 => self.sound_freq_high = val,
0xFF24 => self.sound_channel_volume_control = val,
0xFF25 => self.sound_output_terminal_selector = val,
0xFF26 => self.enabled = val,
_ => panic!("Sound: Write {:02X} to {:04X} unsupported", val, addr),
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
match addr {
0xFF11 => self.sound_length_1,
0xFF12 => self.sound_control_1,
0xFF13 => self.sound_freq_low,
0xFF14 => self.sound_freq_high,
0xFF24 => self.sound_channel_volume_control,
0xFF25 => self.sound_output_terminal_selector,
0xFF26 => self.enabled,
_ => panic!("Sound: Read from {:04X} unsupported", addr),
}
}
}