diff --git a/Makefile b/Makefile index 2bfd360..7b39a9b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ test: - rustc -o appletest --test apple.rs + rustc -Z debug-info -o appletest --test apple.rs ./appletest diff --git a/a2.rs b/a2.rs index dc0d0e3..05867e6 100644 --- a/a2.rs +++ b/a2.rs @@ -6,9 +6,6 @@ static GR_TXMODE: int = 1; static GR_MIXMODE: int = 2; static GR_PAGE1: int = 4; static GR_HIRES: int = 8; -static DBG_CPU: int = 1; -static DBG_RDMEM: int = 2; -static DBG_WRMEM: int = 4; static HW_LO: u16 = 0xC000; static ROM_LO: u16 = 0xD000; @@ -143,6 +140,11 @@ impl AppleII nreads: 0 } } + pub fn set_slot(&mut self, slot: uint, p: ~Peripheral) + { + self.slots[slot] = Some(p); + } + fn noise(&mut self) -> u8 { self.mem[self.nreads & 0xffff] } fn setGrSwitch(&mut self, addr: u16) @@ -173,6 +175,7 @@ impl AppleII fn doIO(&mut self, addr: u16, val: u8) -> u8 { + debug!("doIO({:x}, {:x})", addr, val); let slot = (addr >> 4) & 0x0f; match slot { 0 => self.kbdlatch, // keyboard diff --git a/apple.rs b/apple.rs index 878d3f3..9682194 100644 --- a/apple.rs +++ b/apple.rs @@ -18,7 +18,7 @@ pub mod util; pub mod cpu; pub mod mem; pub mod a2; -//pub mod diskii; +pub mod diskii; mod tests; diff --git a/cpu.rs b/cpu.rs index 438f509..81ec4ce 100644 --- a/cpu.rs +++ b/cpu.rs @@ -297,7 +297,7 @@ macro_rules! decode_op { // No operation 0xea => $this.nop(), - _ => fail!("unimplemented or illegal instruction") + _ => warn!("unimplemented or illegal instruction") } } } diff --git a/diskii.rs b/diskii.rs new file mode 100644 index 0000000..f829f1c --- /dev/null +++ b/diskii.rs @@ -0,0 +1,393 @@ + +use a2::Peripheral; +use std::io::File; + +static NUM_DRIVES: uint = 2; +static NUM_TRACKS: uint = 35; +static RAW_TRACK_SIZE: uint = 0x1a00; +static RAW_SECTOR_SIZE: uint = 383; +static SECTOR_SIZE: uint = 256; +static SECTORS_PER_TRACK: uint = 16; + +type RawTrackData = [u8, ..RAW_TRACK_SIZE]; +type RawDiskData = [RawTrackData, ..NUM_TRACKS]; + +type TrackImage = [u8, ..SECTORS_PER_TRACK*SECTOR_SIZE]; +type DiskImage = [TrackImage, ..NUM_TRACKS]; + +struct Drive +{ + disk_data: RawDiskData, // disk data + track_data: RawTrackData, // array of track data + track: uint, // current track # + track_index: uint, // position of read head along track +} + +static NoDisk: Option = None; + +pub struct DiskController +{ + drives: [Option, ..NUM_DRIVES], + selected: u8, // selected drive (0 or 1) + motor: bool, // is motor on? + read_mode: bool, + write_protect: bool, +} + +impl DiskController +{ + pub fn new() -> DiskController { DiskController { + drives: [NoDisk, NoDisk], + selected: 0, + motor: false, + read_mode: false, + write_protect: false, + } } + + pub fn load_disk(&mut self, disknum: int, imagefilename: &str) + { + let mut f = File::open(&Path::new(imagefilename)); + let mut disk_image : DiskImage = [[0, ..SECTORS_PER_TRACK*SECTOR_SIZE], ..NUM_TRACKS]; + let mut disk_data : RawDiskData = [[0, ..RAW_TRACK_SIZE], ..NUM_TRACKS]; + for track in range(0, NUM_TRACKS) + { + // TODO: check length? + f.read(disk_image[track]); + disk_data[track] = nibblizeTrack(254, track as u8, disk_image); + } + self.drives[disknum] = Some(Drive { + disk_data: disk_data, + track_data: [0, ..RAW_TRACK_SIZE], + track: 0, + track_index: 0, + }); + debug!("loaded disk image {}", imagefilename); + + } + +// fn drive<'r>(&'r self) -> &'r Option<~Drive> { &self.drives[self.selected] } +} + +impl Drive +{ + fn read_latch(&mut self) -> u8 + { + debug!("read latch @ {:x} track {}", self.track_index, self.track) + self.track_index = (self.track_index + 1) % RAW_TRACK_SIZE; + return self.track_data[self.track_index]; + } + + fn write_latch(&mut self, value: u8) + { + debug!("write latch @ {:x} track {}", self.track_index, self.track) + self.track_index = (self.track_index + 1) % RAW_TRACK_SIZE; + self.track_data[self.track_index] = value; + } + + fn servo_phase(&mut self, phase: uint) + { + let mut new_track = self.track; + + // if new phase is even and current phase is odd + if (phase == ((new_track - 1) & 3)) + { + if (new_track > 0) + { + new_track -= 1; + } + } else if (phase == ((new_track + 1) & 3)) + { + if (new_track < NUM_TRACKS*2-1) + { + new_track += 1; + } + } + if ((new_track & 1) == 0) + { + self.track_data = self.disk_data[new_track>>1]; + } else { + // TODO: self.track_data = None; + } + self.track = new_track; + debug!("phase {:x} track = {}", phase, self.track as f32*0.5); + } +} + +impl Peripheral for DiskController +{ + +/* + * Implement the Disk II softswitches that perform the same function whether + * they are read or written to. + */ + fn doHighIO(&mut self, addr: u16, val: u8) -> u8 + { + PROM[addr & 0xff] + } + + fn doIO(&mut self, addr: u16, val: u8) -> u8 + { + //debug!("disk IO {:x} -> {:x}", addr, val); + let &mut drive = &self.drives[self.selected]; + match addr & 0xf + { + /* + * Turn motor phases 0 to 3 on. Turning on the previous phase + 1 + * increments the track position, turning on the previous phase - 1 + * decrements the track position. In this scheme phase 0 and 3 are + * considered to be adjacent. The previous phase number can be + * computed as the track number % 4. + */ + 1|3|5|7 if drive.is_some() => { drive.unwrap().servo_phase(((addr>>1) & 3) as uint); } + /* + * Turn drive motor off. + */ + 8 => { self.motor = false; } + /* + * Turn drive motor on. + */ + 9 => { self.motor = true; } + /* + * Select drive 1. + */ + 0xa => { self.selected = 0; } + /* + * Select drive 2. + */ + 0xb => { self.selected = 1; } + /* + * Select write mode. + */ + 0xf => { self.read_mode = false; } + /* + * Read a disk byte if read mode is active. + */ + 0xc if self.read_mode && drive.is_some() => { return drive.unwrap().read_latch(); } + /* + * Select read mode and read the write protect status. + */ + 0xe => { self.read_mode = true; } + /* + * Write a disk byte if write mode is active and the disk is not + * write protected. + */ + 0xd if !self.read_mode && !self.write_protect && drive.is_some() => { drive.unwrap().write_latch(val); } + /* + * Read the write protect status only. + */ + 0xd if self.write_protect => { return 0x80; } + 0xd => { return 0; } + _ => { return 0; } + } + return 0; //emu.noise(); + } + + +} + +/* --------------- TRACK CONVERSION ROUTINES ---------------------- */ +/* + * Encode a 256-byte sector as SECTOR_SIZE disk bytes as follows: + * + * 14 sync bytes + * 3 address header bytes + * 8 address block bytes + * 3 address trailer bytes + * 6 sync bytes + * 3 data header bytes + * 343 data block bytes + * 3 data trailer bytes + */ + fn nibblizeSector(vol: u8, trk: u8, sector: u8, bytes: &[u8]/*[u8, ..256]*/) -> ~[u8] //[u8, ..RAW_SECTOR_SIZE] + { + assert!(bytes.len() == 256); + /* + * Step 1: write 6 sync bytes (0xff's). Normally these would be + * written as 10-bit bytes with two extra zero bits, but for the + * purpose of emulation normal 8-bit bytes will do, since the + * emulated drive will always be in sync. + */ + + /* + * Step 2: write the 3-byte address header (0xd5 0xaa 0x96). + */ + + /* + * Step 3: write the address block. Use 4-and-4 encoding to convert + * the volume, track and sector and checksum into 2 disk bytes each. + * The checksum is a simple exclusive OR of the first three values. + */ + let chksum = vol^trk^sector; + let address_block = [ + ((vol >> 1) | 0xaa), (vol | 0xaa), + ((trk >> 1) | 0xaa), (trk | 0xaa), + ((sector >> 1) | 0xaa), (sector | 0xaa), + ((chksum >> 1) | 0xaa), (chksum | 0xaa) + ]; + + /* + * Step 4: write the 3-byte address trailer (0xde 0xaa 0xeb). + */ + + /* + * Step 5: write another 6 sync bytes. + */ + + /* + * Step 6: write the 3-byte data header. + */ + + /* + * Step 7: read the next 256-byte sector from the old disk image file, + * and add two zero bytes to bring the number of bytes up to a multiple + * of 3. + */ + let sector_buffer = [bytes.to_owned(), ~[0u8, ..2]].concat_vec(); + + /* + * Step 8: write the first 86 disk bytes of the data block, which + * encodes the bottom two bits of each sector byte into six-bit + * values as follows: + * + * disk byte n, bit 0 = sector byte n, bit 1 + * disk byte n, bit 1 = sector byte n, bit 0 + * disk byte n, bit 2 = sector byte n + 86, bit 1 + * disk byte n, bit 3 = sector byte n + 86, bit 0 + * disk byte n, bit 4 = sector byte n + 172, bit 1 + * disk byte n, bit 5 = sector byte n + 172, bit 0 + * + * The scheme allows each pair of bits to be shifted to the right out + * of the disk byte, then shifted to the left into the sector byte. + * + * Before the 6-bit value is translated to a disk byte, it is exclusive + * ORed with the previous 6-bit value, hence the values written are + * really a running checksum. + */ + let mut prev_value = 0; + let mut value = 0; + let mut data_block_1 = [0, ..86]; + for i in range(0,86) + { + value = (sector_buffer[i] & 0x01) << 1; + value |= (sector_buffer[i] & 0x02) >> 1; + value |= (sector_buffer[i + 86] & 0x01) << 3; + value |= (sector_buffer[i + 86] & 0x02) << 1; + value |= (sector_buffer[i + 172] & 0x01) << 5; + value |= (sector_buffer[i + 172] & 0x02) << 3; + data_block_1[i] = byte_translation[value ^ prev_value]; + prev_value = value; + } + + /* + * Step 9: write the last 256 disk bytes of the data block, which + * encodes the top six bits of each sector byte. Again, each value + * is exclusive ORed with the previous value to create a running + * checksum (the first value is exclusive ORed with the last value of + * the previous step). + */ + + let mut data_block_2 = [0, ..256]; + for i in range(0,256) + { + value = (sector_buffer[i] >> 2); + data_block_2[i] = byte_translation[value ^ prev_value]; + prev_value = value; + } + + /* + * Step 10: write the last value as the checksum. + */ + let checksum = byte_translation[value]; + + /* + * Step 11: write the 3-byte data trailer. + */ + + // concat all the arrays + let result = [ + ~[0xff, ..14], + ~[0xd5, 0xaa, 0x96], + address_block.to_owned(), + ~[0xde, 0xaa, 0xeb], + ~[0xff, ..6], + ~[0xd5, 0xaa, 0xad], + data_block_1.to_owned(), + data_block_2.to_owned(), + ~[checksum], + ~[0xde, 0xaa, 0xeb] + ].concat_vec(); + assert!(result.len() == RAW_SECTOR_SIZE); + result //.slice(0, RAW_SECTOR_SIZE).to_owned() + } + + fn nibblizeTrack(vol:u8, trk:u8, disk:DiskImage) -> RawTrackData + { + use std::vec; + let arr = vec::from_fn(16, |sector| { + let startindex = skewing_table[sector] as uint << 8; + return nibblizeSector(vol, trk, sector as u8, disk[trk].slice(startindex, startindex+256)); + }).concat_vec(); + debug!("track {} converted to {:x} raw bytes", trk, arr.len()); + assert!(arr.len() == RAW_SECTOR_SIZE*16); + let mut fixarr: RawTrackData = [0xff_u8, ..RAW_TRACK_SIZE]; + for i in range(0, arr.len()) + { + fixarr[i] = arr[i]; + } + return fixarr; + } + +static PROM: [u8,..256] = [ + 0xA2,0x20,0xA0,0x00,0xA2,0x03,0x86,0x3C,0x8A,0x0A,0x24,0x3C,0xF0,0x10,0x05,0x3C + ,0x49,0xFF,0x29,0x7E,0xB0,0x08,0x4A,0xD0,0xFB,0x98,0x9D,0x56,0x03,0xC8,0xE8,0x10 + ,0xE5,0x20,0x58,0xFF,0xBA,0xBD,0x00,0x01,0x0A,0x0A,0x0A,0x0A,0x85,0x2B,0xAA,0xBD + ,0x8E,0xC0,0xBD,0x8C,0xC0,0xBD,0x8A,0xC0,0xBD,0x89,0xC0,0xA0,0x50,0xBD,0x80,0xC0 + ,0x98,0x29,0x03,0x0A,0x05,0x2B,0xAA,0xBD,0x81,0xC0,0xA9,0x56, +/*0x20,0xA8,0xFC,*/0xa9,0x00,0xea, + 0x88 + ,0x10,0xEB,0x85,0x26,0x85,0x3D,0x85,0x41,0xA9,0x08,0x85,0x27,0x18,0x08,0xBD,0x8C + ,0xC0,0x10,0xFB,0x49,0xD5,0xD0,0xF7,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0xAA,0xD0,0xF3 + ,0xEA,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0x96,0xF0,0x09,0x28,0x90,0xDF,0x49,0xAD,0xF0 + ,0x25,0xD0,0xD9,0xA0,0x03,0x85,0x40,0xBD,0x8C,0xC0,0x10,0xFB,0x2A,0x85,0x3C,0xBD + ,0x8C,0xC0,0x10,0xFB,0x25,0x3C,0x88,0xD0,0xEC,0x28,0xC5,0x3D,0xD0,0xBE,0xA5,0x40 + ,0xC5,0x41,0xD0,0xB8,0xB0,0xB7,0xA0,0x56,0x84,0x3C,0xBC,0x8C,0xC0,0x10,0xFB,0x59 + ,0xD6,0x02,0xA4,0x3C,0x88,0x99,0x00,0x03,0xD0,0xEE,0x84,0x3C,0xBC,0x8C,0xC0,0x10 + ,0xFB,0x59,0xD6,0x02,0xA4,0x3C,0x91,0x26,0xC8,0xD0,0xEF,0xBC,0x8C,0xC0,0x10,0xFB + ,0x59,0xD6,0x02,0xD0,0x87,0xA0,0x00,0xA2,0x56,0xCA,0x30,0xFB,0xB1,0x26,0x5E,0x00 + ,0x03,0x2A,0x5E,0x00,0x03,0x2A,0x91,0x26,0xC8,0xD0,0xEE,0xE6,0x27,0xE6,0x3D,0xA5 + ,0x3D,0xCD,0x00,0x08,0xA6,0x2B,0x90,0xDB,0x4C,0x01,0x08,0x00,0x00,0x00,0x00,0x00 + ]; + +//static phaseup: [int,..4] = [ 3, 5, 7, 1 ]; +//static phasedn: [int,..4] = [ 7, 1, 3, 5 ]; + + /* + * Normal byte (lower six bits only) -> disk byte translation table. + */ + static byte_translation: [u8, ..64] = [ + 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, + 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, + 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + ]; + + /* + * Sector skewing table. + */ + static skewing_table: [u8, ..16] = [ + 0,7,14,6,13,5,12,4,11,3,10,2,9,1,8,15 + ]; + +// TESTS + +#[test] +fn test_nibbilize() +{ + let disk: DiskImage = [[0, ..SECTORS_PER_TRACK*SECTOR_SIZE], ..NUM_TRACKS]; + let track = nibblizeTrack(254, 0, disk); + //for i in range(0,track.len()) { print!("{:2x} ", track[i]); } +} diff --git a/tests.rs b/tests.rs index 5d76818..bafb968 100644 --- a/tests.rs +++ b/tests.rs @@ -2,6 +2,7 @@ use cpu::Cpu; use mem::Mem; use a2::AppleII; +use diskii::DiskController; // @@ -37,9 +38,12 @@ fn test_a2() { let mut a2 = AppleII::new(); a2.read_roms(); + let mut dc: DiskController = DiskController::new(); + dc.load_disk(0, "JUNK4.DSK"); + a2.set_slot(6, ~dc); let mut cpu = Cpu::new(a2); cpu.reset(); - for i in range(0,100) { + for i in range(0,100000) { cpu.step(); } }