diff --git a/Makefile b/Makefile index 89e64ec..d9e36d5 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ OBJS = armc-start.o armc-cstartup.o armc-cstubs.o armc-cppstubs.o \ exception.o main.o rpi-aux.o rpi-i2c.o rpi-mailbox-interface.o rpi-mailbox.o \ rpi-gpio.o rpi-interrupts.o cache.o ff.o interrupt.o Keyboard.o performance.o \ - Pi1541.o DiskImage.o iec_bus.o iec_commands.o m6502.o m6522.o \ - Drive.o gcr.o prot.o lz.o emmc.o diskio.o options.o Screen.o SSD1306.o ScreenLCD.o \ - Timer.o FileBrowser.o DiskCaddy.o ROMs.o InputMappings.o xga_font_data.o + Drive.o Pi1541.o DiskImage.o iec_bus.o iec_commands.o m6502.o m6522.o \ + gcr.o prot.o lz.o emmc.o diskio.o options.o Screen.o SSD1306.o ScreenLCD.o \ + Timer.o FileBrowser.o DiskCaddy.o ROMs.o InputMappings.o xga_font_data.o m8520.o wd177x.o Pi1581.o SRCDIR = src OBJS := $(addprefix $(SRCDIR)/, $(OBJS)) diff --git a/README.md b/README.md index 86bb31a..8e43e4b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Pi1541 -Commodore 1541 emulator for the Raspberry Pi +Commodore 1541/1581 emulator for the Raspberry Pi Pi1541 is a real-time, cycle exact, Commodore 1541 disk drive emulator that can run on a Raspberry Pi 3B (or 3B+). The software is free and I have endeavored to make the hardware as simple and inexpensive as possible. diff --git a/src/DiskCaddy.cpp b/src/DiskCaddy.cpp index 04e6d4a..1f8b007 100644 --- a/src/DiskCaddy.cpp +++ b/src/DiskCaddy.cpp @@ -159,6 +159,9 @@ bool DiskCaddy::Insert(const FILINFO* fileInfo, bool readOnly) case DiskImage::NBZ: success = InsertNBZ(fileInfo, (unsigned char*)DiskImage::readBuffer, bytesRead, readOnly); break; + case DiskImage::D81: + success = InsertD81(fileInfo, (unsigned char*)DiskImage::readBuffer, bytesRead, readOnly); + break; default: success = false; break; @@ -234,6 +237,19 @@ bool DiskCaddy::InsertNBZ(const FILINFO* fileInfo, unsigned char* diskImageData, return false; } +bool DiskCaddy::InsertD81(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly) +{ + DiskImage diskImage; + if (diskImage.OpenD81(fileInfo, diskImageData, size)) + { + diskImage.SetReadOnly(readOnly); + disks.push_back(diskImage); + selectedIndex = disks.size() - 1; + return true; + } + return false; +} + void DiskCaddy::Display() { unsigned numberOfImages = GetNumberOfImages(); @@ -297,7 +313,7 @@ void DiskCaddy::ShowSelectedImage(u32 index) , numberOfImages , GetImage(index)->GetReadOnly() ? 'R' : ' ' ); - screenLCD->PrintText(false, x, y, buffer, RGBA(0xff, 0xff, 0xff, 0xff), RGBA(0xff, 0xff, 0xff, 0xff)); + screenLCD->PrintText(false, x, y, buffer, 0, RGBA(0xff, 0xff, 0xff, 0xff)); y += LCDFONTHEIGHT; if (numberOfImages > numberOfDisplayedImages && index > numberOfDisplayedImages-1) @@ -321,7 +337,7 @@ void DiskCaddy::ShowSelectedImage(u32 index) memset(buffer, ' ', screenLCD->Width()/screenLCD->GetFontWidth()); screenLCD->PrintText(false, x, y, buffer, BkColour, BkColour); snprintf(buffer, 256, "%d %s", caddyIndex + 1, name); - screenLCD->PrintText(false, x, y, buffer, RGBA(0xff, 0xff, 0xff, 0xff), caddyIndex == index ? RGBA(0xff, 0xff, 0xff, 0xff) : BkColour); + screenLCD->PrintText(false, x, y, buffer, 0, caddyIndex == index ? RGBA(0xff, 0xff, 0xff, 0xff) : BkColour); y += LCDFONTHEIGHT; } if (y >= screenLCD->Height()) diff --git a/src/DiskCaddy.h b/src/DiskCaddy.h index 26cb3e7..d2036a7 100644 --- a/src/DiskCaddy.h +++ b/src/DiskCaddy.h @@ -92,6 +92,7 @@ private: bool InsertG64(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly); bool InsertNIB(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly); bool InsertNBZ(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly); + bool InsertD81(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly); void ShowSelectedImage(u32 index); diff --git a/src/DiskImage.cpp b/src/DiskImage.cpp index bd7e616..1eceb8f 100644 --- a/src/DiskImage.cpp +++ b/src/DiskImage.cpp @@ -32,7 +32,7 @@ extern "C" unsigned char DiskImage::readBuffer[READBUFFER_SIZE]; -static unsigned char compressionBuffer[HALF_TRACK_COUNT * NIB_TRACK_LENGTH]; +static unsigned char compressionBuffer[HALF_TRACK_COUNT * MAX_TRACK_LENGTH]; static const unsigned short SECTOR_LENGTH = 256; static const unsigned short SECTOR_LENGTH_WITH_CHECKSUM = 260; @@ -40,6 +40,52 @@ static const unsigned char GCR_SYNC_BYTE = 0xff; static const unsigned char GCR_GAP_BYTE = 0x55; static const int SECTOR_HEADER_LENGTH = 8; static const unsigned MAX_D64_SIZE = 0x30000; +static const unsigned MAX_D71_SIZE = 0x55600 + 1366; +static const unsigned MAX_D81_SIZE = 822400; + +// CRC-16-CCITT +// CRC(x) = x^16 + x^12 + x^5 + x^0 +unsigned short DiskImage::CRC1021[256] = +{ + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, + 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, + 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, + 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, + 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, + 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, + 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, + 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, + 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, + 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, + 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, + 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, + 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, + 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, + 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 +}; + +void DiskImage::CRC(unsigned short& runningCRC, unsigned char data) +{ + runningCRC = CRC1021[(runningCRC >> 8) ^ data] ^ (runningCRC << 8); +} + +void DiskImage::OutputD81HeaderByte(unsigned char*& dest, unsigned char data) +{ + *dest++ = data; + //crc = CRC1021[(crc >> 8) ^ byte] ^ (crc << 8); + CRC(crc, data); +} + +void DiskImage::OutputD81DataByte(unsigned char*& src, unsigned char*& dest) +{ + unsigned char data; + data = *src++; + *dest++ = data; + //crc = CRC1021[(crc >> 8) ^ data] ^ (crc << 8); + CRC(crc, data); +} + #define NIB_HEADER_SIZE 0xFF @@ -70,20 +116,31 @@ void DiskImage::Close() { case D64: CloseD64(); + memset(tracks, 0x55, sizeof(tracks)); break; case G64: CloseG64(); + memset(tracks, 0x55, sizeof(tracks)); break; case NIB: CloseNIB(); + memset(tracks, 0x55, sizeof(tracks)); break; case NBZ: CloseNBZ(); + memset(tracks, 0x55, sizeof(tracks)); + break; + case D71: + CloseD71(); + memset(tracksD81, 0x55, sizeof(tracksD81)); + break; + case D81: + CloseD81(); + memset(tracksD81, 0, sizeof(tracksD81)); break; default: break; } - memset(tracks, 0x55, sizeof(tracks)); memset(trackLengths, 0, sizeof(trackLengths)); diskType = NONE; fileInfo = 0; @@ -205,7 +262,7 @@ bool DiskImage::WriteD64() f_close(&fp); - f_utime(fileInfo->fname, fileInfo); + //f_utime(fileInfo->fname, fileInfo); SetACTLed(false); DEBUG_LOG("Converted %d blocks into D64 file\r\n", blocks_to_save); @@ -229,6 +286,441 @@ void DiskImage::CloseD64() attachedImageSize = 0; } +bool DiskImage::OpenD71(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size) +{ + Close(); + + this->fileInfo = fileInfo; + + unsigned offset = 0; + + if (size > MAX_D71_SIZE) + size = MAX_D71_SIZE; + + attachedImageSize = size; + + for (unsigned headIndex = 0; headIndex < 2; ++headIndex) + { + for (unsigned halfTrackIndex = 0; halfTrackIndex < D71_HALF_TRACK_COUNT; ++halfTrackIndex) + { + unsigned char track = (halfTrackIndex >> 1); + unsigned char* dest = tracksD81[halfTrackIndex][headIndex]; + + trackLengths[halfTrackIndex] = SectorsPerTrack[track] * GCR_SECTOR_LENGTH; + + if ((halfTrackIndex & 1) == 0) + { + if (offset < size) // This will allow for >35 tracks. + { + trackUsed[halfTrackIndex] = true; + //DEBUG_LOG("Track %d used\r\n", halfTrackIndex); + for (unsigned sectorNo = 0; sectorNo < SectorsPerTrack[track]; ++sectorNo) + { + convert_sector_to_GCR(diskImage + offset, dest, track + 1, sectorNo, diskImage + 0x165A2, 0); + dest += 361; + + offset += SECTOR_LENGTH; + } + } + else + { + trackUsed[halfTrackIndex] = false; + //DEBUG_LOG("Track %d not used\r\n", halfTrackIndex); + } + } + else + { + trackUsed[halfTrackIndex] = false; + //DEBUG_LOG("Track %d not used\r\n", halfTrackIndex); + } + } + } + + diskType = D71; + return true; +} + +bool DiskImage::WriteD71() +{ + if (readOnly) + return true; + + //FIL fp; + //FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE); + //if (res == FR_OK) + //{ + // u32 bytesToWrite; + // u32 bytesWritten; + + // int track, sector; + // BYTE id[3]; + // BYTE d71data[MAXBLOCKSONDISK * 256 * 2], *d71ptr; + // int blocks_to_save = 0; + + // DEBUG_LOG("Writing D71 file...\r\n"); + + // memset(d71data, 0, sizeof(d71data)); + + // if (!GetID(34, id, tracksD81[34][0])) + // { + // DEBUG_LOG("Cannot find directory sector.\r\n"); + // return false; + // } + // d71ptr = d71data; + // //for (track = 0; track <= 40 * 2; track += 2) + // for (track = 0; track <= 35 * 2; track += 2) + // { + // if (trackUsed[track]) + // { + // //printf("Track %d\n", track); + + // for (sector = 0; sector < SectorsPerTrack[track / 2]; sector++) + // { + // ConvertSector(track, sector, d71ptr, tracksD81[track][0]); + // d71ptr += 256; + // blocks_to_save++; + // } + // } + // } + + // bytesToWrite = blocks_to_save * 256; + // SetACTLed(true); + // if (f_write(&fp, d71data, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten) + // { + // SetACTLed(false); + // DEBUG_LOG("Cannot write d71 data.\r\n"); + // f_close(&fp); + // return false; + // } + + // f_close(&fp); + + // //f_utime(fileInfo->fname, fileInfo); + // SetACTLed(false); + + // DEBUG_LOG("Converted %d blocks into D71 file\r\n", blocks_to_save); + + // return true; + //} + //else + { + DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname); + return false; + } +} + +void DiskImage::CloseD71() +{ + if (dirty) + { + WriteD71(); + dirty = false; + } + attachedImageSize = 0; +} + +bool DiskImage::OpenD81(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size) +{ + const unsigned physicalSectors = 10; + unsigned char headIndex; + unsigned headPos; + + Close(); + + this->fileInfo = fileInfo; + + unsigned offsetSource = 0; + + if (size > MAX_D81_SIZE) + size = MAX_D81_SIZE; + + attachedImageSize = size; + + unsigned char* src = diskImage; + + for (unsigned trackIndex = 0; trackIndex < D81_TRACK_COUNT; ++trackIndex) + { + unsigned offsetDest = 0; + unsigned index; + + trackUsed[trackIndex] = true; + memset(trackD81SyncBits[trackIndex][0], 0, MAX_TRACK_LENGTH >> 3); + memset(trackD81SyncBits[trackIndex][1], 0, MAX_TRACK_LENGTH >> 3); +//32x 4e +// For 10 sectors +// 12x 00 // SYNC +// *3x a1 +// 1x fe // ID +// 1x TrackNo (0 indexed) +// 1x Head (0 = 1 1 = 0) +// 1x sector (1 indexed) +// 1x 02 (sector size) +// 1x crc high byte +// 1x crc low byte +// 22x 4e (gap2) +// For 2 loops (logical sectors to physical sectors) +// if first loop +// 12x 00 // SYNC +// *3x a1 +// 1x fb // Data mark +// endif +// 256x data (indexed sequentailly through D81) +// end for +// 1x crc high byte +// 1x crc low byte +// 35x 4e (gap3) +// end for + +// 38/9/0 0x26(38) s9 h1 = 0x60000 27 11 - 27 16d +// 38/10/0 0x26(38) s10 h1 = 0x60200 27 13 - 27 18d +// 38/1/1 0x26(38) s1 h0 = 0x60400 27 15 - 27 20d +// 38/2/2 0x26(38) s2 h0 = 0x60600 27 17 - 27 22d +// 3, 4, 5, 6, 7, 8, 9, 10 +// 37/6/0 0x25(37) s6 h1 = 0x5d200 26 0b - 26 10d +// 37/7/0 0x25(37) s6 h1 = 0x5d400 26 0d - 26 12d + +// sectors 0-19 H1 +// sectors 20-39 H0 + +// 4d800 20 01 +// 4d900 20 02 +// 4da00 20 03 +// 4db00 20 04 +// 4dc00 20 05 +// 4dd00 20 06 +// 4de00 20 07 + +// 50000 21 01 +// 50100 21 02 +// 50200 21 03 +// 50300 21 04 +// 50400 21 05 +// 50500 21 06 +// 50600 21 07 +// 50700 21 08 +// 50800 21 09 +// 50900 21 0a +// 50a00 21 0b +// 50b00 21 0c +// 50c00 21 0d +// 50d00 21 0e +// 50e00 21 0f +// 50f00 21 10 +// 51000 21 11 +// 51100 21 12 +// 51200 21 13 +// 51300 21 14 +// 51400 21 15 +// 51500 21 16 +// 51600 21 17 +// 51700 21 18 +// 51800 21 19 +// 51900 21 1a +// 51a00 21 1b +// 51b00 21 1c +// 51c00 21 1d +// 51d00 21 1e +// 51e00 21 1f +// 51f00 21 20 +// 52000 21 21 +// 52100 21 22 +// 52200 21 23 +// 52300 21 24 +// 52400 21 25 +// 52500 21 26 +// 52600 21 27 +// 52700 20 00 +// 52800 22 01 +// 52900 22 02 +// 52a00 22 03 +// 52b00 22 04 +// 52c00 22 05 +// 52d00 22 06 +// 52e00 22 07 +// 52f00 22 08 +// .. +// 54200 22 1b +// .. +// 54d00 22 26 +// 54e00 22 27 - 22 38d +// 54f00 21 00 - 22 39d +// 55000 23 01 + + unsigned int physicalSectorIndex; + + // (sectors 20 - 39 are on physical side 2) + for (headIndex = 0; headIndex < 2; ++headIndex) + { + unsigned char* dest = tracksD81[trackIndex][headIndex]; + memset(dest, 0x4e, 32); dest += 32; + for (physicalSectorIndex = 0; physicalSectorIndex < physicalSectors; ++physicalSectorIndex) + { + // If a sequence of zeros followed by a sequence of three Sync Bytes is found, then the PLL(phase locked loop) and data separator are synchronized and data bytes can be read. + + memset(dest, 0, 12); dest += 12; // SYNC - This sequence provides to the DPLL enough time to adjust the frequency and center the inspection window. + + headPos = dest - tracksD81[trackIndex][headIndex]; + SetD81SyncBit(trackIndex, headIndex, headPos++, true); + SetD81SyncBit(trackIndex, headIndex, headPos++, true); + SetD81SyncBit(trackIndex, headIndex, headPos++, true); + + // The CRC includes all information starting with the address mark and up to the CRC characters. + // The CRC Register is preset to ones. + crc = 0xffff; + + OutputD81HeaderByte(dest, 0xa1); // Special bytes are encoded that violates the MFM encoding rules with a missing clock in one of the sequential zero bits. + OutputD81HeaderByte(dest, 0xa1); + OutputD81HeaderByte(dest, 0xa1); + OutputD81HeaderByte(dest, 0xfe); // Header ID + OutputD81HeaderByte(dest, (unsigned char)trackIndex); // 0 indexed + OutputD81HeaderByte(dest, headIndex); + OutputD81HeaderByte(dest, (unsigned char)physicalSectorIndex + 1); // 1 indexed + OutputD81HeaderByte(dest, 2); // sector length code (0=128, 1=256, 2=512, 3=1024) + *dest++ = (unsigned char)(crc >> 8); + *dest++ = (unsigned char)(crc & 0xff); + memset(dest, 0x4e, 22); dest += 22; + + memset(dest, 0, 12); dest += 12; // SYNC + + headPos = dest - tracksD81[trackIndex][headIndex]; + SetD81SyncBit(trackIndex, headIndex, headPos++, true); + SetD81SyncBit(trackIndex, headIndex, headPos++, true); + SetD81SyncBit(trackIndex, headIndex, headPos++, true); + + // The CRC Register is preset to ones. + crc = 0xffff; + OutputD81HeaderByte(dest, 0xa1); + OutputD81HeaderByte(dest, 0xa1); + OutputD81HeaderByte(dest, 0xa1); + OutputD81HeaderByte(dest, 0xfb); // Data ID + + for (index = 0; index < D81_SECTOR_LENGTH; ++index) + { + OutputD81DataByte(src, dest); + } + + *dest++ = (unsigned char)(crc >> 8); + *dest++ = (unsigned char)(crc & 0xff); + + memset(dest, 0x4e, 35); dest += 35; + } + + trackLengths[trackIndex] = dest - tracksD81[trackIndex][headIndex]; + } + } + + diskType = D81; + return true; +} + +bool DiskImage::WriteD81() +{ + const unsigned physicalSectors = 10; + + if (readOnly) + return true; + + FIL fp; + FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE); + if (res == FR_OK) + { + u32 bytesToWrite; + u32 bytesWritten; + + for (unsigned trackIndex = 0; trackIndex < D81_TRACK_COUNT; ++trackIndex) + { + unsigned offsetDest = 0; + unsigned index; + + if (trackLengths[trackIndex] != 0 && trackUsed[trackIndex]) + { + unsigned int physicalSectorIndex; + + // (sectors 20 - 39 are on physical side 2) + for (unsigned headIndex = 0; headIndex < 2; ++headIndex) + { + unsigned char* src = tracksD81[trackIndex][headIndex]; + src += 32; + for (physicalSectorIndex = 0; physicalSectorIndex < physicalSectors; ++physicalSectorIndex) + { + // If a sequence of zeros followed by a sequence of three Sync Bytes is found, then the PLL(phase locked loop) and data separator are synchronized and data bytes can be read. + + src += 12; // 12x00 SYNC - This sequence provides to the DPLL enough time to adjust the frequency and center the inspection window. + src += 3; // 3xA1 + src += 1; // 1xFE header ID + src += 1; // 1x track index + src += 1; // 1x head index + src += 1; // 1x physical sector index + src += 1; // 1x sector length code + src += 1; // 1x crc high + src += 1; // 1x crc low + src += 22; // 22x4e + + src += 12; // 12x00 SYNC + src += 3; // 3xA1 + src += 1; // 1xFB header ID + + SetACTLed(true); + bytesToWrite = D81_SECTOR_LENGTH; + if (f_write(&fp, src, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten) + { + SetACTLed(false); + f_close(&fp); + return false; + } + src += D81_SECTOR_LENGTH; + SetACTLed(false); + + src += 1; // 1x crc high + src += 1; // 1x crc low + src += 35; // 35x4e + } + } + } + else + { + const unsigned trackLength = physicalSectors * 2 * D81_SECTOR_LENGTH; + + SetACTLed(true); + for (index = 0; index < trackLength; ++index) + { + unsigned char zero = 0; + bytesToWrite = 1; + if (f_write(&fp, &zero, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten) + { + SetACTLed(false); + f_close(&fp); + return false; + } + } + SetACTLed(false); + } + } + + f_close(&fp); + + //f_utime(fileInfo->fname, fileInfo); + SetACTLed(false); + + return true; + } + else + { + DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname); + return false; + } +} + +void DiskImage::CloseD81() +{ + if (dirty) + { + WriteD81(); + dirty = false; + } + attachedImageSize = 0; +} + bool DiskImage::OpenG64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size) { Close(); @@ -314,10 +806,10 @@ bool DiskImage::WriteG64() BYTE header[12]; DWORD gcr_track_p[MAX_HALFTRACKS_1541] = { 0 }; DWORD gcr_speed_p[MAX_HALFTRACKS_1541] = { 0 }; - BYTE gcr_track[NIB_TRACK_LENGTH + 2]; + BYTE gcr_track[MAX_TRACK_LENGTH + 2]; size_t track_len; int index = 0, track; - BYTE buffer[NIB_TRACK_LENGTH], tempfillbyte; + BYTE buffer[MAX_TRACK_LENGTH], tempfillbyte; DEBUG_LOG("Writing G64 file...\r\n"); //DEBUG_LOG("G64 Track Length = %d", G64_TRACK_MAXLEN); @@ -499,7 +991,7 @@ bool DiskImage::WriteNIB() else { bytesToWrite = NIB_TRACK_LENGTH; - for (track = 0; track < (MAX_TRACKS_1541 * 2); ++track) + for (track = 0; track < HALF_TRACK_COUNT; ++track) { if (trackUsed[track]) { @@ -635,6 +1127,8 @@ DiskImage::DiskType DiskImage::GetDiskImageTypeViaExtention(const char* diskImag return D64; else if (toupper((char)ext[1]) == 'L' && toupper((char)ext[2]) == 'S' && toupper((char)ext[3]) == 'T') return LST; + else if (toupper((char)ext[1]) == 'D' && ext[2] == '8' && ext[3] == '1') + return D81; } return NONE; } @@ -644,6 +1138,30 @@ bool DiskImage::IsDiskImageExtention(const char* diskImageName) return GetDiskImageTypeViaExtention(diskImageName) != NONE; } +bool DiskImage::IsDiskImageD81Extention(const char* diskImageName) +{ + char* ext = strrchr((char*)diskImageName, '.'); + + if (ext) + { + if (toupper((char)ext[1]) == 'D' && ext[2] == '8' && ext[3] == '1') + return true; + } + return false; +} + +bool DiskImage::IsDiskImageD71Extention(const char* diskImageName) +{ + char* ext = strrchr((char*)diskImageName, '.'); + + if (ext) + { + if (toupper((char)ext[1]) == 'D' && ext[2] == '7' && ext[3] == '1') + return true; + } + return false; +} + bool DiskImage::IsLSTExtention(const char* diskImageName) { char* ext = strrchr((char*)diskImageName, '.'); @@ -751,7 +1269,7 @@ int DiskImage::FindSync(unsigned track, int bitIndex, int maxBits, int* syncStar else { bitIndex++; - if (bitIndex >= NIB_TRACK_LENGTH * 8) + if (bitIndex >= MAX_TRACK_LENGTH * 8) bitIndex = 0; byte = tracks[track][bitIndex >> 3]; } diff --git a/src/DiskImage.h b/src/DiskImage.h index 3f2d60b..3fc384a 100644 --- a/src/DiskImage.h +++ b/src/DiskImage.h @@ -21,8 +21,9 @@ #include "types.h" #include "ff.h" -#define READBUFFER_SIZE 1024 * 512 +#define READBUFFER_SIZE 1024 * 512 * 2 // Now need over 800K for D81s +#define MAX_TRACK_LENGTH 0x2000 #define NIB_TRACK_LENGTH 0x2000 #define BAM_OFFSET 4 @@ -35,6 +36,8 @@ #define DIR_ENTRY_NAME_LENGTH 18-2 static const unsigned char HALF_TRACK_COUNT = 84; +static const unsigned char D71_HALF_TRACK_COUNT = 70; +static const unsigned char D81_TRACK_COUNT = 80; static const unsigned short GCR_SYNC_LENGTH = 5; static const unsigned short GCR_HEADER_LENGTH = 10; static const unsigned short GCR_HEADER_GAP_LENGTH = 8; @@ -44,6 +47,8 @@ static const unsigned short GCR_SECTOR_LENGTH = GCR_SYNC_LENGTH + GCR_HEADER_LEN static const unsigned short G64_MAX_TRACK_LENGTH = 7928; +static const unsigned short D81_SECTOR_LENGTH = 512; + class DiskImage { public: @@ -55,6 +60,8 @@ public: NIB, NBZ, LST, + D71, + D81, RAW }; @@ -64,15 +71,17 @@ public: bool OpenG64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size); bool OpenNIB(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size); bool OpenNBZ(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size); - + bool OpenD71(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size); + bool OpenD81(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size); + void Close(); bool GetDecodedSector(u32 track, u32 sector, u8* buffer); inline bool GetNextBit(u32 track, u32 byte, u32 bit) { - if (attachedImageSize == 0) - return 0; + //if (attachedImageSize == 0) + // return 0; return ((tracks[track][byte] >> bit) & 1) != 0; } @@ -103,9 +112,65 @@ public: const char* GetName() { return fileInfo->fname; } inline unsigned BitsInTrack(unsigned track) const { return trackLengths[track] << 3; } + inline unsigned TrackLength(unsigned track) const { return trackLengths[track]; } + + inline bool IsD81() const { return diskType == D81; } + inline unsigned char GetD81Byte(unsigned track, unsigned headIndex, unsigned headPos) const { return tracksD81[track][headIndex][headPos]; } + inline void SetD81Byte(unsigned track, unsigned headIndex, unsigned headPos, unsigned char data) + { + //unsigned headDataOffset; + //if (headPos > 0) + // headDataOffset = headPos - 1; + //else + // headDataOffset = trackLengths[track] - 1; + //if (tracksD81[track][headIndex][headDataOffset] != data) + //{ + // tracksD81[track][headIndex][headDataOffset] = data; + // trackDirty[track] = true; + // trackUsed[track] = true; + // dirty = true; + //} + + if (tracksD81[track][headIndex][headPos] != data) + { + tracksD81[track][headIndex][headPos] = data; + trackDirty[track] = true; + trackUsed[track] = true; + dirty = true; + } + + //unsigned headDataOffset; + //if (headPos < trackLengths[track]) + // headDataOffset = headPos + 1; + //else + // headDataOffset = 0; + //if (tracksD81[track][headIndex][headDataOffset] != data) + //{ + // tracksD81[track][headIndex][headDataOffset] = data; + // trackDirty[track] = true; + // trackUsed[track] = true; + // dirty = true; + //} + + } + + inline bool IsD81ByteASync(unsigned track, unsigned headIndex, unsigned headPos) const + { + return (trackD81SyncBits[track][headIndex][headPos >> 3] & (1 << (headPos & 7))) != 0; + } + inline void SetD81SyncBit(unsigned track, unsigned headIndex, unsigned headPos, bool sync) + { + if (sync) + trackD81SyncBits[track][headIndex][headPos >> 3] |= 1 << (headPos & 7); + else + trackD81SyncBits[track][headIndex][headPos >> 3] &= ~(1 << (headPos & 7)); + + } static DiskType GetDiskImageTypeViaExtention(const char* diskImageName); static bool IsDiskImageExtention(const char* diskImageName); + static bool IsDiskImageD81Extention(const char* diskImageName); + static bool IsDiskImageD71Extention(const char* diskImageName); static bool IsLSTExtention(const char* diskImageName); bool GetReadOnly() const { return readOnly; } @@ -117,16 +182,28 @@ public: static unsigned char readBuffer[READBUFFER_SIZE]; + static void CRC(unsigned short& runningCRC, unsigned char data); + + union + { + unsigned char tracks[HALF_TRACK_COUNT][MAX_TRACK_LENGTH]; + unsigned char tracksD81[HALF_TRACK_COUNT][2][MAX_TRACK_LENGTH]; + }; + private: void CloseD64(); void CloseG64(); void CloseNIB(); void CloseNBZ(); + void CloseD71(); + void CloseD81(); bool WriteD64(); bool WriteG64(); bool WriteNIB(); bool WriteNBZ(); + bool WriteD71(); + bool WriteD81(); inline void TestDirty(u32 track, bool isDirty) { @@ -144,17 +221,26 @@ private: int FindSectorHeader(unsigned track, unsigned sector, unsigned char* id); int FindSync(unsigned track, int bitIndex, int maxBits, int* syncStartIndex = 0); + void OutputD81HeaderByte(unsigned char*& dest, unsigned char byte); + void OutputD81DataByte(unsigned char*& src, unsigned char*& dest); + bool readOnly; bool dirty; unsigned attachedImageSize; DiskType diskType; const FILINFO* fileInfo; - unsigned char tracks[HALF_TRACK_COUNT][NIB_TRACK_LENGTH]; unsigned short trackLengths[HALF_TRACK_COUNT]; - unsigned char trackDensity[HALF_TRACK_COUNT]; + union + { + unsigned char trackDensity[HALF_TRACK_COUNT]; + unsigned char trackD81SyncBits[HALF_TRACK_COUNT][2][MAX_TRACK_LENGTH >> 3]; + }; bool trackDirty[HALF_TRACK_COUNT]; bool trackUsed[HALF_TRACK_COUNT]; + + unsigned short crc; + static unsigned short CRC1021[256]; }; #endif \ No newline at end of file diff --git a/src/FileBrowser.cpp b/src/FileBrowser.cpp index 361807e..ffe2d5f 100644 --- a/src/FileBrowser.cpp +++ b/src/FileBrowser.cpp @@ -254,7 +254,6 @@ void FileBrowser::BrowsableListView::RefreshHighlightScroll() bool FileBrowser::BrowsableListView::CheckBrowseNavigation(bool pageOnly) { - InputMappings* inputMappings = InputMappings::Instance(); bool dirty = false; u32 numberOfEntriesMinus1 = list->entries.size() - 1; @@ -334,7 +333,8 @@ bool FileBrowser::BrowsableListView::CheckBrowseNavigation(bool pageOnly) } FileBrowser::BrowsableList::BrowsableList() - : current(0) + : inputMappings(0) + , current(0) , currentIndex(0) , currentHighlightTime(0) , scrollHighlightRate(0) @@ -375,7 +375,6 @@ void FileBrowser::BrowsableList::RefreshViewsHighlightScroll() bool FileBrowser::BrowsableList::CheckBrowseNavigation() { - InputMappings* inputMappings = InputMappings::Instance(); u32 numberOfEntriesMinus1 = entries.size() - 1; bool dirty = false; @@ -488,8 +487,9 @@ FileBrowser::BrowsableList::Entry* FileBrowser::BrowsableList::FindEntry(const c return 0; } -FileBrowser::FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate) - : state(State_Folders) +FileBrowser::FileBrowser(InputMappings* inputMappings, DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate) + : inputMappings(inputMappings) + , state(State_Folders) , diskCaddy(diskCaddy) , selectionsMade(false) , roms(roms) @@ -508,11 +508,11 @@ FileBrowser::FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool di rows = 1; folder.scrollHighlightRate = scrollHighlightRate; - folder.AddView(screenMain, columns, rows, positionX, positionY, false); + folder.AddView(screenMain, inputMappings, columns, rows, positionX, positionY, false); positionX = screenMain->ScaleX(1024 - 320); columns = screenMain->ScaleX(40); - caddySelections.AddView(screenMain, columns, rows, positionX, positionY, false); + caddySelections.AddView(screenMain, inputMappings, columns, rows, positionX, positionY, false); @@ -523,7 +523,7 @@ FileBrowser::FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool di positionX = 0; positionY = 0; - folder.AddView(screenLCD, columns, rows, positionX, positionY, true); + folder.AddView(screenLCD, inputMappings, columns, rows, positionX, positionY, true); } } @@ -894,8 +894,6 @@ void FileBrowser::UpdateCurrentHighlight() void FileBrowser::Update() { - InputMappings* inputMappings = InputMappings::Instance(); - if ( inputMappings->CheckKeyboardBrowseMode() || inputMappings->CheckButtonsBrowseMode() || (folder.searchPrefixIndex != 0) ) UpdateInputFolders(); @@ -977,7 +975,6 @@ bool FileBrowser::AddImageToCaddy(FileBrowser::BrowsableList::Entry* current) void FileBrowser::UpdateInputFolders() { - InputMappings* inputMappings = InputMappings::Instance(); bool dirty = false; if (inputMappings->BrowseFunction()) diff --git a/src/FileBrowser.h b/src/FileBrowser.h index b41bfa2..e345b7a 100644 --- a/src/FileBrowser.h +++ b/src/FileBrowser.h @@ -26,6 +26,7 @@ #include "DiskCaddy.h" #include "ROMs.h" #include "ScreenBase.h" +#include "InputMappings.h" #define VIC2_COLOUR_INDEX_BLACK 0 #define VIC2_COLOUR_INDEX_WHITE 1 @@ -59,8 +60,9 @@ public: class BrowsableListView { public: - BrowsableListView(BrowsableList* list, ScreenBase* screen, u32 columns, u32 rows, u32 positionX, u32 positionY, bool lcdPgUpDown) + BrowsableListView(BrowsableList* list, InputMappings* inputMappings, ScreenBase* screen, u32 columns, u32 rows, u32 positionX, u32 positionY, bool lcdPgUpDown) : list(list) + , inputMappings(inputMappings) , screen(screen) , columns(columns) , rows(rows) @@ -81,6 +83,7 @@ public: BrowsableList* list; u32 offset; + InputMappings* inputMappings; ScreenBase* screen; u32 columns; @@ -111,9 +114,10 @@ public: } } - void AddView(ScreenBase* screen, u32 columns, u32 rows, u32 positionX, u32 positionY, bool lcdPgUpDown) + void AddView(ScreenBase* screen, InputMappings* inputMappings, u32 columns, u32 rows, u32 positionX, u32 positionY, bool lcdPgUpDown) { - BrowsableListView view(this, screen, columns, rows, positionX, positionY, lcdPgUpDown); + this->inputMappings = inputMappings; + BrowsableListView view(this, inputMappings, screen, columns, rows, positionX, positionY, lcdPgUpDown); views.push_back(view); } @@ -153,6 +157,7 @@ public: void RefreshViewsHighlightScroll(); bool CheckBrowseNavigation(); + InputMappings* inputMappings; std::vector entries; Entry* current; u32 currentIndex; @@ -166,7 +171,7 @@ public: std::vector views; }; - FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate); + FileBrowser(InputMappings* inputMappings, DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate); void SelectAutoMountImage(const char* image); void DisplayRoot(); @@ -219,6 +224,8 @@ private: bool SelectROMOrDevice(u32 index); + InputMappings* inputMappings; + enum State { State_Folders, diff --git a/src/InputMappings.h b/src/InputMappings.h index d0a8221..cec7a8f 100644 --- a/src/InputMappings.h +++ b/src/InputMappings.h @@ -18,7 +18,6 @@ #ifndef InputMappings_H #define InputMappings_H -#include "Singleton.h" #include "Keyboard.h" #define ESC_FLAG (1 << 0) @@ -50,10 +49,10 @@ // dont exceed 32!! -class InputMappings : public Singleton +class InputMappings //: public Singleton { protected: - friend Singleton; +// friend Singleton; unsigned keyboardFlags; unsigned buttonFlags; diff --git a/src/Keyboard.cpp b/src/Keyboard.cpp index c763adc..c3f843c 100644 --- a/src/Keyboard.cpp +++ b/src/Keyboard.cpp @@ -29,6 +29,8 @@ extern "C" #define REPEAT_RATE 8 #define REPEAT_DELAY 3 +Keyboard* Keyboard::instance; + void Keyboard::KeyPressedHandlerRaw(TUSBKeyboardDevice* device, unsigned char modifiers, const unsigned char RawKeys[6]) { // byte 0 - modifires @@ -147,6 +149,7 @@ Keyboard::Keyboard() , updateCount(0) , updateCountLastRead(-1) { + instance = this; keyStatus[0] = 0; keyStatus[1] = 0; diff --git a/src/Keyboard.h b/src/Keyboard.h index 2a62ddd..cfbd1e6 100644 --- a/src/Keyboard.h +++ b/src/Keyboard.h @@ -18,7 +18,6 @@ #ifndef Keyboard_H #define Keyboard_H -#include "Singleton.h" #include extern "C" @@ -284,18 +283,18 @@ extern "C" #define KEY_MEDIA_REFRESH 0xfa #define KEY_MEDIA_CALC 0xfb -class Keyboard : public Singleton +class Keyboard //: public Singleton { protected: - friend Singleton; + //friend Singleton; u8 modifier; - volatile u64 keyStatus[2]; - volatile u64 keyStatusPrev[2]; + /*volatile*/ u64 keyStatus[2]; + /*volatile*/ u64 keyStatusPrev[2]; u32 keyRepeatCount[MAX_KEYS]; u32 timer; //volatile bool dirty; - volatile u32 updateCount; + /*volatile*/ u32 updateCount; u32 updateCountLastRead; static void KeyPressedHandlerRaw(TUSBKeyboardDevice* device, unsigned char modifiers, const unsigned char RawKeys[6]); @@ -304,6 +303,8 @@ protected: public: Keyboard(); + static Keyboard* Instance() { return instance; } + //inline u32 UpdateCount() const { return updateCount; } inline bool CheckChanged() @@ -342,12 +343,16 @@ public: } inline bool KeyAnyHeld() - { return (keyStatus[0] | keyStatus[1]); } + { + return (keyStatus[0] | keyStatus[1]); + } inline bool KeyEitherAlt() { return (modifier & (KEY_MOD_LALT | KEY_MOD_RALT) ); } inline bool KeyNoModifiers() { return (!modifier ); } inline bool KeyLCtrlAlt() { return (modifier == (KEY_MOD_LALT | KEY_MOD_LCTRL) ); } + + static Keyboard* instance; }; #endif diff --git a/src/Pi1541.cpp b/src/Pi1541.cpp index 799898f..fc059a3 100644 --- a/src/Pi1541.cpp +++ b/src/Pi1541.cpp @@ -18,6 +18,147 @@ #include "Pi1541.h" #include "debug.h" +#include "options.h" +#include "ROMs.h" + +extern Options options; +extern Pi1541 pi1541; +extern u8 s_u8Memory[0xc000]; +extern ROMs roms; + +/////////////////////////////////////////////////////////////////////////////////////// +// 6502 Address bus functions. +// Move here out of Pi1541 to increase performance. +/////////////////////////////////////////////////////////////////////////////////////// +// In a 1541 address decoding and chip selects are performed by a 74LS42 ONE-OF-TEN DECODER +// 74LS42 Ouputs a low to the !CS based on the four inputs provided by address bits 10-13 +// 1800 !cs2 on pin 9 +// 1c00 !cs2 on pin 7 +u8 read6502(u16 address) +{ + u8 value = 0; + if (address & 0x8000) + { + switch (address & 0xe000) // keep bits 15,14,13 + { + case 0x8000: // 0x8000-0x9fff + if (options.GetRAMBOard()) { + value = s_u8Memory[address]; // 74LS42 outputs low on pin 1 or pin 2 + break; + } + case 0xa000: // 0xa000-0xbfff + case 0xc000: // 0xc000-0xdfff + case 0xe000: // 0xe000-0xffff + value = roms.Read(address); + break; + } + } + else + { + // Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding + u16 addressLines12_11_10 = (address & 0x1c00) >> 10; + switch (addressLines12_11_10) + { + case 0: + case 1: + value = s_u8Memory[address & 0x7ff]; // 74LS42 outputs low on pin 1 or pin 2 + break; + case 6: + value = pi1541.VIA[0].Read(address); // 74LS42 outputs low on pin 7 + break; + case 7: + value = pi1541.VIA[1].Read(address); // 74LS42 outputs low on pin 9 + break; + default: + value = address >> 8; // Empty address bus + break; + } + } + return value; +} + +// Allows a mode where we have RAM at all addresses other than the ROM and the VIAs. (Maybe useful to someone?) +u8 read6502ExtraRAM(u16 address) +{ + if (address & 0x8000) + { + return roms.Read(address); + } + else + { + u16 addressLines11And12 = address & 0x1800; + if (addressLines11And12 == 0x1800) return pi1541.VIA[(address & 0x400) != 0].Read(address); // address line 10 indicates what VIA to index + return s_u8Memory[address & 0x7fff]; + } +} + +// Use for debugging (Reads VIA registers without the regular VIA read side effects) +u8 peek6502(u16 address) +{ + u8 value; + if (address & 0x8000) // address line 15 selects the ROM + { + value = roms.Read(address); + } + else + { + // Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding + u16 addressLines15_12_11_10 = (address & 0x1c00) >> 10; + addressLines15_12_11_10 |= (address & 0x8000) >> (15 - 3); + if (addressLines15_12_11_10 == 0 || addressLines15_12_11_10 == 1) value = s_u8Memory[address & 0x7ff]; // 74LS42 outputs low on pin 1 or pin 2 + else if (addressLines15_12_11_10 == 6) value = pi1541.VIA[0].Peek(address); // 74LS42 outputs low on pin 7 + else if (addressLines15_12_11_10 == 7) value = pi1541.VIA[1].Peek(address); // 74LS42 outputs low on pin 9 + else value = address >> 8; // Empty address bus + } + return value; +} + +void write6502(u16 address, const u8 value) +{ + if (address & 0x8000) + { + switch (address & 0xe000) // keep bits 15,14,13 + { + case 0x8000: // 0x8000-0x9fff + if (options.GetRAMBOard()) { + s_u8Memory[address] = value; // 74LS42 outputs low on pin 1 or pin 2 + break; + } + case 0xa000: // 0xa000-0xbfff + case 0xc000: // 0xc000-0xdfff + case 0xe000: // 0xe000-0xffff + return; + } + } + else + { + // Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding + u16 addressLines12_11_10 = (address & 0x1c00) >> 10; + switch (addressLines12_11_10) + { + case 0: + case 1: + s_u8Memory[address & 0x7ff] = value; // 74LS42 outputs low on pin 1 or pin 2 + break; + case 6: + pi1541.VIA[0].Write(address, value); // 74LS42 outputs low on pin 7 + break; + case 7: + pi1541.VIA[1].Write(address, value); // 74LS42 outputs low on pin 9 + break; + default: + break; + } + } +} + +void write6502ExtraRAM(u16 address, const u8 value) +{ + if (address & 0x8000) return; // address line 15 selects the ROM + u16 addressLines11And12 = address & 0x1800; + if (addressLines11And12 == 0) s_u8Memory[address & 0x7fff] = value; + else if (addressLines11And12 == 0x1800) pi1541.VIA[(address & 0x400) != 0].Write(address, value); // address line 10 indicates what VIA to index +} Pi1541::Pi1541() { diff --git a/src/Pi1541.h b/src/Pi1541.h index 5e688cf..570509e 100644 --- a/src/Pi1541.h +++ b/src/Pi1541.h @@ -42,6 +42,18 @@ public: M6502 m6502; + enum PortPins + { + VIAPORTPINS_DEVSEL0 = 0x20, //pb5 + VIAPORTPINS_DEVSEL1 = 0x40, //pb6 + }; + + inline void SetDeviceID(u8 id) + { + VIA[0].GetPortB()->SetInput(VIAPORTPINS_DEVSEL0, id & 1); + VIA[0].GetPortB()->SetInput(VIAPORTPINS_DEVSEL1, id & 2); + } + private: //u8 Memory[0xc000]; diff --git a/src/Pi1581.cpp b/src/Pi1581.cpp new file mode 100644 index 0000000..c50d731 --- /dev/null +++ b/src/Pi1581.cpp @@ -0,0 +1,299 @@ +// Pi1541 - A Commodore 1541 disk drive emulator +// Copyright(C) 2018 Stephen White +// +// This file is part of Pi1541. +// +// Pi1541 is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Pi1541 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Pi1541. If not, see . + +#include "Pi1581.h" +#include "iec_bus.h" +#include "options.h" +#include "ROMs.h" +#include "debug.h" + +extern Pi1581 pi1581; +extern u8 s_u8Memory[0xc000]; +extern ROMs roms; + +// PA0 SIDE0 +// PA1 !RDY +// PA2 !MOTOR +// PA3 ID 1 +// PA4 ID 2 +// PA5 POWER LED +// PA6 ACT LED +// PA7 !DISK_CHNG +// PB0 DATA IN +// PB1 DATA OUT +// PB2 CLK IN +// PB3 CLK OUT +// PB4 ATNA +// PB5 FAST SER DIR +// PB6 /WPAT +// PB7 ATN IN + +// FAST SER DIR sets the direction of U13 (74ls241) +// When 1 +// - SP is sent to DATA +// - Fast Clock (SRQ) is sent to CNT +// When 0 +// - DATA is sent to SP +// - CNT is sent to Fast Clock (SRQ) + +enum PortPins +{ + // PORT A + PORTA_PINS_SIDE0 = 0x01, //pa0 + PORTA_PINS_RDY = 0x20, //pa1 + PORTA_PINS_MOTOR = 0x04, //pa2 + + PORTA_PINS_DEVSEL0 = 0x08, //pa3 + PORTA_PINS_DEVSEL1 = 0x10, //pa4 + + PORTA_PINS_ACT_LED = 0x40, //pa6 + PORTA_PINS_DISKCHNG = 0x80, //pa7 + + // PORT B + PORTB_PINS_FAST_SER_DIR = 0x20, //pb5 + PORTB_PINS_WPAT = 0x40, //pb6 +}; + +enum +{ + FAST_SERIAL_DIR_IN, + FAST_SERIAL_DIR_OUT +}; + +extern u16 pc; + +// CS +// 8520 +// $4000 +// +// 1770 +// $6000 +// +// ROM +// $8000 +// +// RAM +// 0-$1fff + +u8 read6502_1581(u16 address) +{ + u8 value = 0; + if (address & 0x8000) + { + value = roms.Read1581(address); + } + else if (address >= 0x6000) + { + value = pi1581.wd177x.Read(address); + //DEBUG_LOG("177x r %04x %02x %04x\r\n", address, value, pc); + } + else if (address >= 0x4000) + { + value = pi1581.CIA.Read(address); + //DEBUG_LOG("CIA r %04x %02x %04x\r\n", address, value, pc); + } + else if (address < 0x2000) + { + value = s_u8Memory[address & 0x1fff]; + } + else + { + value = address >> 8; // Empty address bus + } + return value; +} + +// Use for debugging (Reads VIA registers without the regular VIA read side effects) +u8 peek6502_1581(u16 address) +{ + u8 value = 0; + return value; +} + +void write6502_1581(u16 address, const u8 value) +{ + if (address & 0x8000) + { + return; + } + else if (address >= 0x6000) + { + //DEBUG_LOG("177x w %04x %02x %04x\r\n", address, value, pc); + pi1581.wd177x.Write(address, value); + } + else if (address >= 0x4000) + { + //DEBUG_LOG("CIA w %04x %02x %04x\r\n", address, value, pc); + pi1581.CIA.Write(address, value); + } + else if (address < 0x2000) + { + s_u8Memory[address & 0x1fff] = value; + } +} + +static void CIAPortA_OnPortOut(void* pUserData, unsigned char status) +{ + Pi1581* pi1581 = (Pi1581*)pUserData; + + pi1581->wd177x.SetSide(status & PORTA_PINS_SIDE0); + + bool motorAsserted = (status & PORTA_PINS_MOTOR) == 0; + + if (motorAsserted) + { + if (!pi1581->wd177x.IsExternalMotorAsserted()) + { + pi1581->CIA.GetPortA()->SetInput(PORTA_PINS_RDY, true); + + pi1581->RDYDelayCount = 250000; + pi1581->wd177x.AssertExternalMotor(motorAsserted); // !MOTOR + } + } + else + { + pi1581->RDYDelayCount = 0; + pi1581->CIA.GetPortA()->SetInput(PORTA_PINS_RDY, true); + if (pi1581->wd177x.IsExternalMotorAsserted()) + { + //DEBUG_LOG("pc=%04x\r\n", pc); + pi1581->wd177x.AssertExternalMotor(motorAsserted); // !MOTOR + } + } + + pi1581->SetLED((status & PORTA_PINS_ACT_LED) != 0); +} + +static void CIAPortB_OnPortOut(void* pUserData, unsigned char status) +{ + Pi1581* pi1581 = (Pi1581*)pUserData; + + pi1581->wd177x.SetWPRTPin(status & PORTB_PINS_WPAT); // !WPAT + + if (status & PORTB_PINS_FAST_SER_DIR) + pi1581->fastSerialDirection = FAST_SERIAL_DIR_OUT; + else + pi1581->fastSerialDirection = FAST_SERIAL_DIR_IN; + + + IEC_Bus::PortB_OnPortOut(0, status); +} + +Pi1581::Pi1581() +{ + Initialise(); +} + +void Pi1581::Initialise() +{ + LED = false; + + CIA.ConnectIRQ(&m6502.IRQ); + // IRQ is not connected on a 1581 + //wd177x.ConnectIRQ(&m6502.IRQ); + + CIA.GetPortA()->SetPortOut(this, CIAPortA_OnPortOut); + CIA.GetPortB()->SetPortOut(this, CIAPortB_OnPortOut); + + // For now disk is writable + CIA.GetPortB()->SetInput(PORTB_PINS_WPAT, true); + + CIA.GetPortA()->SetInput(PORTA_PINS_DISKCHNG, false); + CIA.GetPortA()->SetInput(PORTA_PINS_RDY, true); + + RDYDelayCount = 0; +} + +void Pi1581::Update() +{ + //CIA.GetPortA()->SetInput(PORTA_PINS_DISKCHNG, 1); + //CIA.GetPortA()->SetInput(PORTA_PINS_RDY, false); + + if (RDYDelayCount) + { + RDYDelayCount--; + if (RDYDelayCount == 0) + { + CIA.GetPortA()->SetInput(PORTA_PINS_RDY, false); + } + } + + CIA.Execute(); + + // SRQ is pulled high by the c128 + + // When U13 (74LS241) is set to fast serial IN + // - R20 pulls SP high + // - R19 pulls CNT high + // - R26 pulls Fast Clock (SRQ) in high + // When U13 (74LS241) is set to fast serial OUT + // - R25 pulls DATA high + // - R28 pulls Fast Clock (SRQ) out high + + if (fastSerialDirection == FAST_SERIAL_DIR_OUT) + { + // When 1 + // - SP is sent to DATA + // - Fast Clock (SRQ) is sent to CNT + IEC_Bus::SetFastSerialData(!CIA.GetPinSP()); // Communication on fast serial is done after the inverter. + IEC_Bus::SetFastSerialSRQ(CIA.GetPinCNT()); + //trace lines and see it this needs to set other lines + } + else + { + // When 0 + // - DATA is sent to SP + // - CNT is sent to Fast Clock (SRQ) + CIA.SetPinSP(!IEC_Bus::GetPI_Data()); // Communication on fast serial is done before the inverter. + CIA.SetPinCNT(IEC_Bus::GetPI_SRQ()); + } + + for (int i = 0; i < 4; ++i) + { + wd177x.Execute(); + } +} + +void Pi1581::Reset() +{ + IOPort* CIABPortB; + + fastSerialDirection = FAST_SERIAL_DIR_IN; + CIA.Reset(); + wd177x.Reset(); + IEC_Bus::Reset(); + // On a real drive the outputs look like they are being pulled high (when set to inputs) (Taking an input from the front end of an inverter) + CIABPortB = CIA.GetPortB(); + CIABPortB->SetInput(VIAPORTPINS_DATAOUT, true); + CIABPortB->SetInput(VIAPORTPINS_CLOCKOUT, true); + CIABPortB->SetInput(VIAPORTPINS_ATNAOUT, true); +} + +void Pi1581::SetDeviceID(u8 id) +{ + CIA.GetPortA()->SetInput(PORTA_PINS_DEVSEL0, id & 1); + CIA.GetPortA()->SetInput(PORTA_PINS_DEVSEL1, id & 2); +} + +void Pi1581::Insert(DiskImage* diskImage) +{ +// CIA.GetPortB()->SetInput(PORTB_PINS_WPAT, !diskImage->GetReadOnly()); + CIA.GetPortA()->SetInput(PORTA_PINS_DISKCHNG, true); + wd177x.Insert(diskImage); +} + diff --git a/src/Pi1581.h b/src/Pi1581.h new file mode 100644 index 0000000..312c140 --- /dev/null +++ b/src/Pi1581.h @@ -0,0 +1,68 @@ +// Pi1541 - A Commodore 1541 disk drive emulator +// Copyright(C) 2018 Stephen White +// +// This file is part of Pi1541. +// +// Pi1541 is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Pi1541 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Pi1541. If not, see . + +#ifndef PI1581_H +#define PI1581_H + +#include "Drive.h" +#include "m6502.h" +#include "iec_bus.h" +#include "wd177x.h" +#include "m8520.h" + +class Pi1581 +{ + +public: + Pi1581(); + + void Initialise(); + + void Update(); + + void Reset(); + + void SetDeviceID(u8 id); + + void Insert(DiskImage* diskImage); + + inline bool IsLEDOn() const { return LED; } + inline bool IsMotorOn() const { return wd177x.IsExternalMotorAsserted(); } + inline void SetLED(bool value) { LED = value; } + //Drive drive; + WD177x wd177x; + m8520 CIA; + + M6502 m6502; + + unsigned fastSerialDirection; + unsigned int RDYDelayCount; + +private: + bool LED; + + //u8 Memory[0xc000]; + + //static u8 Read6502(u16 address, void* data); + //static u8 Read6502ExtraRAM(u16 address, void* data); + //static u8 Peek6502(u16 address, void* data); + //static void Write6502(u16 address, const u8 value, void* data); + //static void Write6502ExtraRAM(u16 address, const u8 value, void* data); +}; + +#endif \ No newline at end of file diff --git a/src/ROMs.h b/src/ROMs.h index 8e01a95..09b3709 100644 --- a/src/ROMs.h +++ b/src/ROMs.h @@ -30,13 +30,20 @@ public: { return ROMImages[currentROMIndex][address & 0x3fff]; } + inline u8 Read1581(u16 address) + { + return ROMImage1581[address & 0x7fff]; + } void ResetCurrentROMIndex(); static const int ROM_SIZE = 16384; + static const int ROM1581_SIZE = 16384 * 2; static const int MAX_ROMS = 7; unsigned char ROMImages[MAX_ROMS][ROM_SIZE]; + unsigned char ROMImage1581[ROM1581_SIZE]; + char ROMName1581[256]; char ROMNames[MAX_ROMS][256]; bool ROMValid[MAX_ROMS]; diff --git a/src/iec_bus.cpp b/src/iec_bus.cpp index f669716..3d7627f 100644 --- a/src/iec_bus.cpp +++ b/src/iec_bus.cpp @@ -29,6 +29,7 @@ u32 IEC_Bus::PIGPIO_MASK_IN_RESET = 1 << PIGPIO_RESET; bool IEC_Bus::PI_Atn = false; bool IEC_Bus::PI_Data = false; bool IEC_Bus::PI_Clock = false; +bool IEC_Bus::PI_SRQ = false; bool IEC_Bus::PI_Reset = false; bool IEC_Bus::VIA_Atna = false; @@ -38,6 +39,11 @@ bool IEC_Bus::VIA_Clock = false; bool IEC_Bus::DataSetToOut = false; bool IEC_Bus::AtnaDataSetToOut = false; bool IEC_Bus::ClockSetToOut = false; +bool IEC_Bus::SRQSetToOut = false; + +m6522* IEC_Bus::VIA = 0; +m8520* IEC_Bus::CIA = 0; +IOPort* IEC_Bus::port = 0; bool IEC_Bus::OutputLED = false; bool IEC_Bus::OutputSound = false; @@ -58,7 +64,6 @@ u32 IEC_Bus::inputRepeatThreshold[5]; u32 IEC_Bus::inputRepeat[5] = { 0 }; u32 IEC_Bus::inputRepeatPrev[5] = { 0 }; -m6522* IEC_Bus::VIA = 0; u32 IEC_Bus::emulationModeCheckButtonIndex = 0; @@ -111,7 +116,7 @@ void IEC_Bus::ReadBrowseMode(void) Resetting = !ignoreReset && ((gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0)); } -void IEC_Bus::ReadEmulationMode(void) +void IEC_Bus::ReadEmulationMode1541(void) { IOPort* portB = 0; unsigned gplev0 = read32(ARM_GPIO_GPLEV0); @@ -126,14 +131,15 @@ void IEC_Bus::ReadEmulationMode(void) //emulationModeCheckButtonIndex++; //emulationModeCheckButtonIndex %= buttonCount; - portB = VIA->GetPortB(); + portB = port; bool ATNIn = (gplev0 & PIGPIO_MASK_IN_ATN) == (invertIECInputs ? PIGPIO_MASK_IN_ATN : 0); if (PI_Atn != ATNIn) { PI_Atn = ATNIn; - //if (VIA) + //DEBUG_LOG("A%d\r\n", PI_Atn); + //if (port) { if ((portB->GetDirection() & 0x10) != 0) { @@ -182,3 +188,90 @@ void IEC_Bus::ReadEmulationMode(void) Resetting = !ignoreReset && ((gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0)); } + +void IEC_Bus::ReadEmulationMode1581(void) +{ + IOPort* portB = 0; + unsigned gplev0 = read32(ARM_GPIO_GPLEV0); + + int buttonIndex; + for (buttonIndex = 0; buttonIndex < 3; ++buttonIndex) + { + UpdateButton(buttonIndex, gplev0); + } + // Doing it this way screws with the debounce counters. + //UpdateButton(emulationModeCheckButtonIndex, gplev0); + //emulationModeCheckButtonIndex++; + //emulationModeCheckButtonIndex %= buttonCount; + + portB = port; + + bool ATNIn = (gplev0 & PIGPIO_MASK_IN_ATN) == (invertIECInputs ? PIGPIO_MASK_IN_ATN : 0); + if (PI_Atn != ATNIn) + { + PI_Atn = ATNIn; + + //DEBUG_LOG("A%d\r\n", PI_Atn); + //if (port) + { + if ((portB->GetDirection() & 0x10) != 0) + { + // Emulate the XOR gate UD3 + // We only need to do this when fully emulating, iec commands do this internally + AtnaDataSetToOut = (VIA_Atna & PI_Atn); + } + + portB->SetInput(VIAPORTPINS_ATNIN, ATNIn); //is inverted and then connected to pb7 and ca1 + CIA->SetPinFLAG(!ATNIn); + } + } + + if (portB && (portB->GetDirection() & 0x10) == 0) + AtnaDataSetToOut = false; // If the ATNA PB4 gets set to an input then we can't be pulling data low. (Maniac Mansion does this) + + if (!AtnaDataSetToOut && !DataSetToOut) // only sense if we have not brought the line low (because we can't as we have the pin set to output but we can simulate in software) + { + bool DATAIn = (gplev0 & PIGPIO_MASK_IN_DATA) == (invertIECInputs ? PIGPIO_MASK_IN_DATA : 0); + if (PI_Data != DATAIn) + { + PI_Data = DATAIn; + portB->SetInput(VIAPORTPINS_DATAIN, DATAIn); // VIA DATAin pb0 output from inverted DIN 5 DATA + } + } + else + { + PI_Data = true; + portB->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software + } + + if (!ClockSetToOut) // only sense if we have not brought the line low (because we can't as we have the pin set to output but we can simulate in software) + { + bool CLOCKIn = (gplev0 & PIGPIO_MASK_IN_CLOCK) == (invertIECInputs ? PIGPIO_MASK_IN_CLOCK : 0); + if (PI_Clock != CLOCKIn) + { + PI_Clock = CLOCKIn; + portB->SetInput(VIAPORTPINS_CLOCKIN, CLOCKIn); // VIA CLKin pb2 output from inverted DIN 4 CLK + } + } + else + { + PI_Clock = true; + portB->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software + } + + if (!SRQSetToOut) // only sense if we have not brought the line low (because we can't as we have the pin set to output but we can simulate in software) + { + bool SRQIn = (gplev0 & PIGPIO_MASK_IN_SRQ) == (invertIECInputs ? PIGPIO_MASK_IN_SRQ : 0); + if (PI_SRQ != SRQIn) + { + PI_SRQ = SRQIn; + } + } + else + { + PI_SRQ = false; + } + + Resetting = !ignoreReset && ((gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0)); +} + diff --git a/src/iec_bus.h b/src/iec_bus.h index 91df8ef..2249db3 100644 --- a/src/iec_bus.h +++ b/src/iec_bus.h @@ -21,6 +21,7 @@ #include "debug.h" #include "m6522.h" +#include "m8520.h" #include "rpi-gpio.h" #include "rpiHardware.h" @@ -66,6 +67,11 @@ // Therefore in the same vein if PB7 is set to output it could cause the input of the XOR to be pulled low // +// NOTE ABOUT SRQ +// SRQ is a little bit different. +// The 1581 does not pull it high. Only the 128 pulls it high. +// + enum PIGPIO { // Original Non-split lines @@ -83,7 +89,8 @@ enum PIGPIO // 3 I2C_CLK //5 PIGPIO_IN_BUTTON4 = 4, // 07 Common PIGPIO_IN_BUTTON5 = 5, // 29 Common - PIGPIO_OUT_RESET = 6, // 31 + //PIGPIO_OUT_RESET = 6, // 31 + PIGPIO_OUT_SPI0_RS = 6, // 31 // 7 SPI0_CS1 //26 // 8 SPI0_CS0 //24 // 9 SPI0_MISO //21 @@ -218,8 +225,6 @@ enum VIAPortPins VIAPORTPINS_CLOCKIN = 0x04, //pb2 VIAPORTPINS_CLOCKOUT = 0x08,//pb3 VIAPORTPINS_ATNAOUT = 0x10, //pb4 - VIAPORTPINS_DEVSEL0 = 0x20, //pb5 - VIAPORTPINS_DEVSEL1 = 0x40, //pb5 VIAPORTPINS_ATNIN = 0x80 //bp7 }; @@ -260,7 +265,9 @@ public: RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON1, FS_INPUT); - RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_RESET, FS_OUTPUT); + //RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_RESET, FS_OUTPUT); + RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_SPI0_RS, FS_OUTPUT); + RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_ATN, FS_OUTPUT); RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_SOUND, FS_OUTPUT); RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_LED, FS_OUTPUT); @@ -337,7 +344,8 @@ public: } static void ReadBrowseMode(void); - static void ReadEmulationMode(void); + static void ReadEmulationMode1541(void); + static void ReadEmulationMode1581(void); static void WaitUntilReset(void) { @@ -365,21 +373,28 @@ public: VIA_Data = (status & (unsigned char)VIAPORTPINS_DATAOUT) != 0; // VIA DATAout PB1 inverted and then connected to DIN DATA VIA_Clock = (status & (unsigned char)VIAPORTPINS_CLOCKOUT) != 0; // VIA CLKout PB3 inverted and then connected to DIN CLK - // Emulate the XOR gate UD3 - AtnaDataSetToOut = (VIA_Atna != PI_Atn); + if (VIA) + { + // Emulate the XOR gate UD3 + AtnaDataSetToOut = (VIA_Atna != PI_Atn); + } + else + { + AtnaDataSetToOut = (VIA_Atna & PI_Atn); + } if (AtnaDataSetToOut) { // if the output of the XOR gate is high (ie VIA_Atna != PI_Atn) then this is inverted and pulls DATA low (activating it) PI_Data = true; - if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software + if (port) port->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software } - if (VIA) + if (VIA && port) { // If the VIA's data and clock outputs ever get set to inputs the real hardware reads these lines as asserted. - bool PB1SetToInput = (VIA->GetPortB()->GetDirection() & 2) == 0; - bool PB3SetToInput = (VIA->GetPortB()->GetDirection() & 8) == 0; + bool PB1SetToInput = (port->GetDirection() & 2) == 0; + bool PB3SetToInput = (port->GetDirection() & 8) == 0; if (PB1SetToInput) VIA_Data = true; if (PB3SetToInput) VIA_Clock = true; } @@ -390,18 +405,18 @@ public: if (!oldDataSetToOut && DataSetToOut) { PI_Data = true; - if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_DATAOUT, true); // simulate the read in software + if (port) port->SetInput(VIAPORTPINS_DATAOUT, true); // simulate the read in software } if (!oldClockSetToOut && ClockSetToOut) { PI_Clock = true; - if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software + if (port) port->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software } } - static inline void RefreshOuts(void) + static inline void RefreshOuts1541(void) { unsigned set = 0; unsigned clear = 0; @@ -419,8 +434,6 @@ public: } else { - clear |= 1 << PIGPIO_OUT_ATN; - if (AtnaDataSetToOut || DataSetToOut) set |= 1 << PIGPIO_OUT_DATA; else clear |= 1 << PIGPIO_OUT_DATA; @@ -444,6 +457,51 @@ public: write32(ARM_GPIO_GPCLR0, clear); } + static inline void RefreshOuts1581(void) + { + unsigned set = 0; + unsigned clear = 0; + unsigned tmp; + + if (!splitIECLines) + { + unsigned outputs = 0; + + if (AtnaDataSetToOut || DataSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_DATA - 10) * 3)); + if (ClockSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_CLOCK - 10) * 3)); + //if (SRQSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_SRQ - 10) * 3)); // For Option A hardware we should not support pulling more than 2 lines low at any one time! + + unsigned nValue = (myOutsGPFSEL1 & PI_OUTPUT_MASK_GPFSEL1) | outputs; + write32(ARM_GPIO_GPFSEL1, nValue); + } + else + { + if (AtnaDataSetToOut || DataSetToOut) set |= 1 << PIGPIO_OUT_DATA; + else clear |= 1 << PIGPIO_OUT_DATA; + + if (ClockSetToOut) set |= 1 << PIGPIO_OUT_CLOCK; + else clear |= 1 << PIGPIO_OUT_CLOCK; + + if (!SRQSetToOut) set |= 1 << PIGPIO_OUT_SRQ; // fast clock is pulled high but we have an inverter in our hardware so to compensate we invert in software now + else clear |= 1 << PIGPIO_OUT_SRQ; + + if (!invertIECOutputs) { + tmp = set; + set = clear; + clear = tmp; + } + } + + if (OutputLED) set |= 1 << PIGPIO_OUT_LED; + else clear |= 1 << PIGPIO_OUT_LED; + + if (OutputSound) set |= 1 << PIGPIO_OUT_SOUND; + else clear |= 1 << PIGPIO_OUT_SOUND; + + write32(ARM_GPIO_GPSET0, set); + write32(ARM_GPIO_GPCLR0, clear); + } + static void WaitMicroSeconds(u32 amount) { u32 count; @@ -461,6 +519,17 @@ public: } } + /////////////////////////////////////////////////////////////////////////////////////////////// + // 1581 Fast Serial + static inline void SetFastSerialData(bool value) + { + DataSetToOut = value; + } + static inline void SetFastSerialSRQ(bool value) + { + SRQSetToOut = value; + } + /////////////////////////////////////////////////////////////////////////////////////////////// // Manual methods used by IEC_Commands static inline void AssertData() @@ -468,7 +537,7 @@ public: if (!DataSetToOut) { DataSetToOut = true; - RefreshOuts(); + RefreshOuts1541(); } } static inline void ReleaseData() @@ -476,7 +545,7 @@ public: if (DataSetToOut) { DataSetToOut = false; - RefreshOuts(); + RefreshOuts1541(); } } @@ -485,7 +554,7 @@ public: if (!ClockSetToOut) { ClockSetToOut = true; - RefreshOuts(); + RefreshOuts1541(); } } static inline void ReleaseClock() @@ -493,10 +562,11 @@ public: if (ClockSetToOut) { ClockSetToOut = false; - RefreshOuts(); + RefreshOuts1541(); } } + static inline bool GetPI_SRQ() { return PI_SRQ; } static inline bool GetPI_Atn() { return PI_Atn; } static inline bool IsAtnAsserted() { return PI_Atn; } static inline bool IsAtnReleased() { return !PI_Atn; } @@ -542,6 +612,7 @@ public: PI_Atn = !PI_Atn; PI_Data = !PI_Data; PI_Clock = !PI_Clock; + PI_SRQ = !PI_SRQ; PI_Reset = !PI_Reset; } } @@ -562,6 +633,8 @@ public: // CA2, CB1 and CB2 are not connected // - check if pulled high or low static m6522* VIA; + static m8520* CIA; + static IOPort* port; static inline void Reset(void) { @@ -578,15 +651,21 @@ public: DataSetToOut = false; ClockSetToOut = false; + SRQSetToOut = false; PI_Atn = false; PI_Data = false; PI_Clock = false; + PI_SRQ = false; + + if (VIA) + AtnaDataSetToOut = (VIA_Atna != PI_Atn); + else + AtnaDataSetToOut = (VIA_Atna & PI_Atn); - AtnaDataSetToOut = (VIA_Atna != PI_Atn); if (AtnaDataSetToOut) PI_Data = true; - RefreshOuts(); + RefreshOuts1581(); } static bool GetInputButtonPressed(int buttonIndex) { return InputButton[buttonIndex] && !InputButtonPrev[buttonIndex]; } @@ -615,6 +694,7 @@ private: static bool PI_Atn; static bool PI_Data; static bool PI_Clock; + static bool PI_SRQ; static bool PI_Reset; static bool VIA_Atna; @@ -624,6 +704,7 @@ private: static bool DataSetToOut; static bool AtnaDataSetToOut; static bool ClockSetToOut; + static bool SRQSetToOut; static bool Resetting; static u32 myOutsGPFSEL0; diff --git a/src/m8520.cpp b/src/m8520.cpp new file mode 100644 index 0000000..e90028f --- /dev/null +++ b/src/m8520.cpp @@ -0,0 +1,864 @@ +// Pi1541 - A Commodore 1541 disk drive emulator +// Copyright(C) 2018 Stephen White +// +// This file is part of Pi1541. +// +// Pi1541 is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Pi1541 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Pi1541. If not, see . + +#include "m8520.h" + +// The 8520 contains a programmable baud rate generator which is used for fast serial transfers. +// Timer A is used for the baud rate generator. In the output mode data is shifted out on SP at 1/2 the underflow rate of Timer A. +// The maximum baud rate possible is phi 2 divided by 4, but the maximum usable baud rate will be determined by line loading and the speed at which the receiver responds to the input data. +// Transmission will start following a write to the Serial Data Register (provided Timer A is running and in continuous mode). +// The clock derived from Timer A appears on the CNT pin. +// The Data in the Serial Data Register will be loaded into the shift register then shifted out to the SP pin. +// After 8 pulses on the CNT pin, a bit in the ICR (interrupt control register) is set and if desired, an interrupt may be generated. +// All incoming fast bytes generate an interrupt within the Fast Serial Drive. +// Bytes are shifted out; most significant bit first. + +// The serial port is a buffered, 8-bit synchronous shift register system. +// A control bit selects input or output mode. +// In input mode, data on the SP pin is shifted into the shift register on the rising edge of the signal applied to the CNT pin. +// After 8 CNT pulses, the data in the shift register is dumped into the Serial Data Register and an interrupt is generated. +// In the output mode, TIMER A is used for the baud rate generator. +// Data is shifted out on the SP pin at 1/2 the underflow rate of TIMER A. +// The maximum baud rate possible is (212 divided by 4, but the maximum useable baud rate will be determined byline loading and the speed at which the receiver responds to input data. +// Transmission will start following a write to the Serial Data Register (provided TIMER A is running and in continuous mode). +// The clock signal derived from TIMER A appears as an output on the CNT pin. +// The data in the Serial Data Register will be loaded into the shift register then shift out to the SP pin when a CNT pulse occurs. +// Datashifted out becomes valid on the falling edge of CNT and remains valid until the next falling edge. +// After 8 CNT pulses, an interrupt is generated to indicate more data can be sent. +// If the Serial Data Register was loaded with new information prior to this interrupt, the new data will automatically be loaded into the shift register and transmission will continue. +// If the microprocessor stays one byte ahead of the shift register, transmission will be continuous. +// If no further data is to be transmitted, after the 8th CNT pulse, CNT will return high and SP will remain at the level of the last data bit transmitted. +// SDR data is shifted out MSB first and serial input data should also appear in this format. +// The bidirectional capability of the Serial Port and CNT clock allows many 6526 devices to be connected to a common serial communication bus on which one 6526 acts as a master, +// sourcing data and shift clock, while all other 6526 chips act as slaves. +// Both CNT and SP outputs are open drain to allow such a common bus. +// Protocol for master / slave selection can be transmitted over the serial bus, or via dedicated handshaking lines. + +// Reset +// sdr_valid = 0 +// sr_bits = 0 + +// SR write +// SR = value +// if in output mode +// sdr_valid = 1 +// SR Read +// value = SR +// +// Update +// if TA times out +// if in ouput +// if sr_bits +// sr_bits-- +// if sr_bits == 0 +// flag IRQ +// SR = shifter +// endif +// endif +// if sr_bits == 0 && sdr_valid +// shifter = SR +// sdr_valid = 0 +// sr_bits = 14 +// endif +// endif + + +// A control bit allows the timer output to appear on a PORT B output line(PB6 for TIMER A and PB7 for TIMER B). +// This function overrides the DDRB control bit and forces the appropriate PB line to an output. + +extern u16 pc; +extern bool bLoggingCYCs; + + +m8520::m8520() +{ + Reset(); +} + +void m8520::Reset() +{ + // The port pins are set as inputs and port registers to zero(although a read of the ports will return all highs because of passive pullups). + portA.SetDirection(0); + portB.SetDirection(0); + + PCAsserted = 0; + + FLAGPin = true; // external devices should be setting this + CNTPin = false; // external devices should be setting this + CNTPinOld = false; + SPPin = false; // external devices should be setting this + TODPin = false; // external devices should be setting this + + //CRARegister = 0; + //CRBRegister = 0; + Write(CRA, 0); + Write(CRB, 0); + + // The timer control registers are set to zero and the timer latches to all ones. + timerACounter = 0; + timerALatch = 0xffff; + timerAActive = false; + timerAOutputOnPB6 = false; + timerAToggle = false; + timerAOneShot = false; + timerAMode = TA_MODE_PHI2; + timerA50Hz = false; + //timerATimeOutCount = 0; + ta_pb6 = true; + timerAReloaded = false; + + timerBCounter = 0; + timerBLatch = 0xffff; + timerBActive = false; + timerBOutputOnPB7 = false; + timerBToggle = false; + timerBOneShot = false; + timerBMode = TB_MODE_PHI2; + timerBAlarm = false; + tb_pb7 = true; + timerBReloaded = false; + + serialPortMode = SP_MODE_INPUT; + serialPortRegister = 0; + serialShiftRegister = 0; + serialBitsShiftedSoFar = 8; + + TODActive = false; + TODAlarm = 0; + TODClock = 0; + TODLatch = 0; + + ICRMask = 0; + ICRData = 0; + //OutputIRQ(); +} + +extern u16 pc; + +// Update for a single cycle +void m8520::Execute() +{ + bool timerATimedOut = false; + bool timerBTimedOut = false; + // In oneshot mode, the timer will count down from the latched value to zero, generate an interrupt, reload the latched value, then stop. + // In continuous mode, the timer will count from the latched value to zero, generate an interrupt, reload the latched value and repeat the procedure continuously. + + // The timer latch is loaded into the timer on any timer underflow + if (timerAActive && !timerAReloaded) + { + switch (timerAMode) + { + case m8520::TA_MODE_PHI2: + timerATimedOut = timerACounter == 0; + timerACounter--; + break; + case m8520::TA_MODE_CNT_PVE: + if (serialPortMode == SP_MODE_OUTPUT) + { + if (CNTPin && !CNTPinOld) + { + timerATimedOut = timerACounter == 0; + timerACounter--; // counts positive CNT transitions. + } + } + break; + } + + //timerATimedOut = timerACounter == 0; + + if (timerATimedOut) + { + //timerATimeOutCount++; + + SetInterrupt(IR_TA); + + ReloadTimerA(); + + if (timerAOneShot) + { + timerAActive = false; + } + else + { + if (serialPortMode == SP_MODE_OUTPUT) + { + //The individual data bits now appear at half the timeout rate of timer A on the SP line and the clock signal from timer A + // appears on the CNT line(it changes value on each timeout so that the next bit appears on the SP line on each negative transition[high to low]). + // The transfer begins with the MSB of the data byte.Once all eight bits have been output, CNT remains high and the SP line retains the value of the last bit sent + // in addition, the SP bit in the interrupt control register is set to show that the shift register can be supplied with new data. + //DEBUG_LOG("o %d\r\n", serialBitsShiftedSoFar); + + if (serialBitsShiftedSoFar >= 8) + { + // If no further data is to be transmitted, after the 8th CNT pulse, CNT will return high and SP will remain at the level of the last data bit transmitted. + CNTPin = true; + } + else + { + // Data is shifted out on the SP pin at 1 / 2 the underflow rate of TIMER A. + // (provided TIMER A is running and in continuous mode) + + bool oldCNT = CNTPin; + // The clock signal derived from TIMER A appears as an output on the CNT pin. + CNTPin = !CNTPin; + + // Datashifted out becomes valid on the falling edge of CNT and remains valid until the next falling edge. + if (!CNTPin) //(timerATimeOutCount & 1) == 0) + { + // SDR data is shifted out MSB first and serial input data should also appear in this format. + SPPin = (serialShiftRegister & 0x80) != 0; + serialShiftRegister <<= 1; + + //DEBUG_LOG("o%d\r\n", serialBitsShiftedSoFar); + + serialBitsShiftedSoFar++; + + if (serialBitsShiftedSoFar == 8) + { + //DEBUG_LOG("o %04x\r\n", pc); + SetInterrupt(IR_SDR); + } + } + } + } + //else + //{ + // CNTPin = true; + //} + } + + ta_pb6 = !ta_pb6; + //if (timerAOutputOnPB6) + //{ + // // This function overrides the DDRB control bit and forces the appropriate PB line to an output. + // unsigned char ddr = portB.GetDirection(); + // if (ddr & 0x80) + // { + // // the signal on PB6 is inverted each time the counter reaches zero + // if (!ta_pb6) portB.SetOutput(portB.GetOutput() & (~0x40)); + // else portB.SetOutput(portB.GetOutput() | 0x40); + // } + //} + } + } + + //timerBTimedOut = timerBCounter == 0; + + if (timerBActive && !timerBReloaded) + { + //DEBUG_LOG("TB %04x\r\n", timerBCounter); + + switch (timerBMode) + { + case m8520::TB_MODE_PHI2: + timerBTimedOut = timerBCounter == 0; + timerBCounter--; + break; + case m8520::TB_MODE_CNT_PVE: + if (serialPortMode == SP_MODE_OUTPUT) + { + if (CNTPin && !CNTPinOld) + { + timerBTimedOut = timerBCounter == 0; + timerBCounter--; // counts positive CNT transitions. + } + } + break; + case m8520::TB_MODE_TA_UNDEFLOW: + if (timerATimedOut) + { + timerBTimedOut = timerBCounter == 0; + timerBCounter--; + } + break; + case m8520::TB_MODE_TA_UNDEFLOW_CNT_PVE: + if (serialPortMode == SP_MODE_OUTPUT) + { + if (timerATimedOut && CNTPin) + { + timerBTimedOut = timerBCounter == 0; + timerBCounter--; + } + } + break; + } + + if (timerBTimedOut) + { + //DEBUG_LOG("TB out\r\n"); + SetInterrupt(IR_TB); + + ReloadTimerB(); + + if (timerBOneShot) + { + timerBActive = false; + } + + tb_pb7 = !tb_pb7; + //if (timerBOutputOnPB7) + //{ + // // This function overrides the DDRB control bit and forces the appropriate PB line to an output. + // unsigned char ddr = portB.GetDirection(); + // if (ddr & 0x80) + // { + // // the signal on PB7 is inverted each time the counter reaches zero + // if (!tb_pb7) portB.SetOutput(portB.GetOutput() & (~0x80)); + // else portB.SetOutput(portB.GetOutput() | 0x80); + // } + //} + } + } + + //switch (serialPortMode) + //{ + // case SP_MODE_OUTPUT: + + // break; + // case SP_MODE_INPUT: + // // input mode is handled by the rising edge of CNT in SetPinCNT + // break; + //} + + if (PCAsserted) + PCAsserted--; + + CNTPinOld = CNTPin; + timerAReloaded = false; + timerBReloaded = false; +} + +void m8520::SetPinFLAG(bool value) // Active low +{ + if (FLAGPin && !value) + { + // Any negative transition on FLAG will set the FLAG interrupt bit. + SetInterrupt(IR_FLG); + //DEBUG_LOG("IR_FLG\r\n"); + } + FLAGPin = value; +} + +void m8520::SetPinCNT(bool value) +{ + if (serialPortMode == SP_MODE_INPUT) + { + if (!CNTPin && value) // rising edge? + { + //DEBUG_LOG("C%d\r\n", serialBitsShiftedSoFar); + if (serialBitsShiftedSoFar < 8) + { + // In input mode, data on the SP pin is shifted into the shift register on the rising edge of the signal applied to the CNT pin. + // After 8 CNT pulses, the data in the shift register is dumped into the Serial Data Register and an interrupt is generated. + + serialShiftRegister <<= 1; + serialShiftRegister |= SPPin; + + //DEBUG_LOG("i%d\r\n", serialBitsShiftedSoFar); + serialBitsShiftedSoFar++; + + if (serialBitsShiftedSoFar == 8) + { + //DEBUG_LOG("ib=%02x %d\r\n", serialShiftRegister, pc); + serialPortRegister = serialShiftRegister; + //serialBitsShiftedSoFar = 0; + SetInterrupt(IR_SDR); + } + } + } + CNTPin = value; + } +} + +void m8520::SetPinSP(bool value) +{ + SPPin = value; +} + + +void m8520::SetPinTOD(bool value) +{ + // Posistive edge transitions on this pin cause the binary counter to increment. + if (value && !TODPin && TODActive) + { + TODClock++; + TODClock &= 0xffffff; + if (TODClock == TODAlarm) + { + SetInterrupt(IR_TOD); + } + } + TODPin = value; +} + +unsigned char m8520::Read(unsigned int address) +{ + unsigned char value = 0; + + switch (address & 0xf) + { + case ORA: + value = ReadPortA(); + break; + case ORB: + value = ReadPortB(); + // The 8520 datasheet contradicts itself;- + // PC will go low forone cycle following a read orwrite of PORT B. + // PC will go low on the 3rd cycle after a PORT B access. + PCAsserted = 3; + break; + case DDRA: + value = portA.GetDirection(); + break; + case DDRB: + value = portB.GetDirection(); + break; + + // Data read from the timer are the present contents of the Timer Counter. + case TALO: + value = timerACounter & 0xff; + break; + case TAHI: + value = timerACounter >> 8; + break; + case TBLO: + value = timerACounter & 0xff; + break; + case TBHI: + value = timerACounter >> 8; + break; + + // Since a carry from one stage to the next can occur at any time with respect to a read operation, a latching function is included to keep all Time of Day information constant during a read sequence. + // All TOD registers latch on a read of MSB event and remain latched until after a read of LSB Event. + // The TOD clock continues to count when the output registers are latched. + // If only one register is to be read, there is no carry problem and the register can be read “on the fly", provided that any read of MSB Event is followed by a read of LSB Event to disable the latching. + case EVENT_LSB: + value = (unsigned char)(TODLatch); + break; + case EVENT_8_15: + value = (unsigned char)(TODLatch >> 8); + break; + case EVENT_MSB: + TODLatch = TODClock; + value = (unsigned char)(TODLatch >> 16); + break; + + + case NC: + break; + case SDR: + value = serialPortRegister; + //DEBUG_LOG("rsr%02x\r\n", value); + //serialBitsShiftedSoFar = 0; + break; + case ICR: + // The interrupt DATA register is cleared and the IRQ line returns high following a read of the DATA register. + value = ICRData; + //if (ICRData & IR_FLG) + //{ + // DEBUG_LOG("IRFLG %04x\r\n", pc); + // bLoggingCYCs = true; + //} + ClearInterrupt(ICRData & (IR_FLG | IR_SDR | IR_TOD | IR_TB | IR_TA)); + ICRData = 0; + break; + case CRA: + value = CRARegister; + break; + case CRB: + value = CRBRegister; + break; + } + return value; +} + +unsigned char m8520::Peek(unsigned int address) +{ + unsigned char value = 0; + + switch (address & 0xf) + { + case ORA: + value = PeekPortA(); + break; + case ORB: + value = PeekPortB(); + break; + case DDRA: + value = portA.GetDirection(); + break; + case DDRB: + value = portB.GetDirection(); + break; + case TALO: + value = timerACounter & 0xff; + break; + case TAHI: + value = timerACounter >> 8; + break; + case TBLO: + value = timerACounter & 0xff; + break; + case TBHI: + value = timerACounter >> 8; + break; + case EVENT_LSB: + value = (unsigned char)(TODLatch); + break; + case EVENT_8_15: + value = (unsigned char)(TODLatch >> 8); + break; + case EVENT_MSB: + TODLatch = TODClock; + value = (unsigned char)(TODLatch >> 16); + break; + case NC: + break; + case SDR: + value = serialPortRegister; + break; + case ICR: + value = ICRData; + break; + case CRA: + // bit 4 will always read back a zero and writing a zero has no effect + value = CRARegister; + break; + case CRB: + value = CRBRegister; + break; + } + return value; +} + +void m8520::Write(unsigned int address, unsigned char value) +{ + unsigned char ddr; + + switch (address & 0xf) + { + case ORA: + WritePortA(value); + break; + case ORB: + WritePortB(value); + // The 8520 datasheet contradicts itself;- + // PC will go low forone cycle following a read orwrite of PORT B. + // PC will go low on the 3rd cycle after a PORT B access. + PCAsserted = 3; + break; + case DDRA: + portA.SetDirection(value); + break; + case DDRB: + portB.SetDirection(value); + break; + + // Data written to the timer are latched in the Timer Latch. + case TALO: + timerALatch = (timerBLatch & 0xff00) | value; + break; + case TAHI: + timerALatch = (timerBLatch & 0xff) | (value << 8); + // In oneshot mode; a write to Timer High will transfer the timer latch to the counter and initiate counting regardless of the start bit. + + // The timer latch is loaded into the timer following a write to the high byte of the prescaler while the timer is stopped. + + // The timer latch is loaded into the timer on any timer underflow, on a force load or following a write to the high byte of the prescaler while the timer is stopped. + // If the timer is running, a write to the high byte will load the timer latch, but not reload the counter. + + if (!timerAActive/* || timerAOneShot*/) + ReloadTimerA(); + break; + case TBLO: + timerBLatch = (timerBLatch & 0xff00) | value; + break; + case TBHI: + timerBLatch = (timerBLatch & 0xff) | (value << 8); + // In oneshot mode; a write to Timer High will transfer the timer latch to the counter and initiate counting regardless of the start bit. + + // The timer latch is loaded into the timer following a write to the high byte of the prescaler while the timer is stopped. + if (!timerBActive/* || timerBOneShot*/) + ReloadTimerB(); + break; + + + // TOD is automatically stopped whenever a write to the regiser occurs. + case EVENT_LSB: + if (timerBAlarm) + { + TODAlarm = (TODAlarm & 0xffff00) | value; + } + else + { + TODActive = true; // The clock will not start again until after a write to the LSB Event Register. + TODClock = (TODClock & 0xffff00) | value; + } + break; + case EVENT_8_15: + if (timerBAlarm) + { + TODAlarm = (TODAlarm & 0xff00ff) | ((unsigned)value << 8); + } + else + { + TODActive = false; + TODClock = (TODClock & 0xff00ff) | ((unsigned)value << 8); + } + break; + case EVENT_MSB: + if (timerBAlarm) + { + TODAlarm = (TODAlarm & 0xffff) | ((unsigned)value << 16); + } + else + { + TODActive = false; + TODClock = (TODClock & 0xffff) | ((unsigned)value << 16); + } + break; + + case NC: + break; + case SDR: + //DEBUG_LOG("wsr%02x %04x\r\n", value, pc); + serialPortRegister = value; + //serialShiftRegister = value; + if ((CRARegister & CRA_SPMODE)) + { + serialBitsShiftedSoFar = 0; + //DEBUG_LOG("SDR W 0\r\n"); + } + break; + case ICR: + // The MASK register provides convenient control of Individual mask bits. When writing to the MASK register, + // if bit 7 (SET / CLEAR) of the data written is a ZERO, any mask bit written with a one will be cleared, while + // those mask bits written with a zero will be unaffected. If bit 7 of the data written is a ONE, any mask bit written + // with a one will be set, while those mask bits written with a zero will be unaffected. + // In order for an interrupt flag to set IR and generate an Interrupt Request, the corresponding MASK bit must be set. + if ((value & IR_SET) == 0) + ICRMask &= ~(value & (IR_FLG | IR_SDR | IR_TOD | IR_TB | IR_TA)); + else + ICRMask |= (value & (IR_FLG | IR_SDR | IR_TOD | IR_TB | IR_TA)); + + //DEBUG_LOG("irqm %02x %04x\r\n", ICRMask, pc); + + OutputIRQ(); + break; + case CRA: + { + unsigned char CRARegisterOld = CRARegister; + + CRARegister = value; + if (CRARegister & CRA_START) + { + // Timer A start + timerAActive = true; + } + else + { + // Timer A stop + timerAActive = false; + } + + if (CRARegister & CRA_PBON) + { + // Timer A output appears on PB6 + timerAOutputOnPB6 = true; + } + else + { + // PB6 normal operation + timerAOutputOnPB6 = false; + } + + if (CRARegister & CRA_OUTPUTMODE) + { + // Toggle + timerAToggle = true; + + // The toggle output is set high whenever the timer is started and is set low by RES + } + else + { + // Pulse + timerAToggle = false; + } + + if (CRARegister & CRA_RUNMODE) + { + // One shot + + if (!timerAOneShot) + { + ReloadTimerA(); + } + + timerAOneShot = true; + } + else + { + // Continuous + timerAOneShot = false; + } + + // bit 4 will always read back a zero and writing a zero has no effect + if (CRARegister & CRA_LOAD) + { + // Force load + // A strobe bit allows the timer latch to be loaded into the timer counter at any time, whether the timer is running or not. + ReloadTimerA(); + } + + if (CRARegister & CRA_INMODE) + { + // Timer A counts positive CNT transitions + timerAMode = TA_MODE_CNT_PVE; + } + else + { + // A counts phi2 + timerAMode = TA_MODE_PHI2; + } + + //if ((CRARegisterOld ^ CRARegister) & CRA_SPMODE) + if ((CRARegisterOld & CRA_SPMODE) ^ (CRARegister & CRA_SPMODE)) + { + if (CRARegister & CRA_SPMODE) + { + // Serial port output - CNT sources shift clock + serialPortMode = SP_MODE_OUTPUT; + //DEBUG_LOG("o %04x\r\n", pc); + //DEBUG_LOG("o\r\n"); + + serialBitsShiftedSoFar = 8; + serialShiftRegister = 0; + } + else + { + // Serial port input - (external shift clock required) + serialPortMode = SP_MODE_INPUT; + + //DEBUG_LOG("i %04x\r\n", pc); + //DEBUG_LOG("i\r\n"); + + serialBitsShiftedSoFar = 0; + serialShiftRegister = 0; + } + } + + if (CRARegister & CRA_TODIN) + { + timerA50Hz = true; + } + else + { + timerA50Hz = false; + } + + break; + } + case CRB: + CRBRegister = value; + + CRBRegister = value; + if (CRBRegister & CRB_START) + { + // Timer B start + timerBActive = true; + //DEBUG_LOG("TB A\r\n"); + } + else + { + // Timer B stop + timerBActive = false; + } + + if (CRBRegister & CRB_PBON) + { + // Timer A output appears on PB6 + timerBOutputOnPB7 = true; + } + else + { + // PB6 normal operation + timerBOutputOnPB7 = false; + } + + + // Toggle / Pulse + // A control bit selects the output applied to PORT B. + // On every timer underflow the output can either toggle or generate a single positive pulse of one cycle duration. + // The toggle output is set high whenever the timer is started and is set low by RES. + if (CRBRegister & CRB_OUTPUTMODE) + { + // Toggle + timerBToggle = true; + } + else + { + // Pulse + timerBToggle = false; + } + + if (CRBRegister & CRB_RUNMODE) + { + // One shot + timerBOneShot = true; + } + else + { + // Continuous + timerBOneShot = false; + } + + // bit 4 will always read back a zero and writing a zero has no effect + if (CRBRegister & CRB_LOAD) + { + // Force load + // A strobe bit allows the timer latch to be loaded into the timer counter at any time, whether the timer is running or not. + ReloadTimerB(); + } + + switch ((CRBRegister & (CRB_INMODE1 | CRB_INMODE0)) >> 5) + { + case 0: + timerBMode = TB_MODE_PHI2; + break; + case 1: + timerBMode = TB_MODE_CNT_PVE; + break; + case 2: + timerBMode = TB_MODE_TA_UNDEFLOW; + break; + case 3: + timerBMode = TB_MODE_TA_UNDEFLOW_CNT_PVE; + break; + } + + if (CRBRegister & CRB_ALARM) + { + timerBAlarm = true; + } + else + { + timerBAlarm = false; + } + break; + } +} diff --git a/src/m8520.h b/src/m8520.h new file mode 100644 index 0000000..d38923a --- /dev/null +++ b/src/m8520.h @@ -0,0 +1,294 @@ +// Pi1541 - A Commodore 1541 disk drive emulator +// Copyright(C) 2018 Stephen White +// +// This file is part of Pi1541. +// +// Pi1541 is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Pi1541 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Pi1541. If not, see . + +#ifndef M8520_H +#define M8520_H + +#include "IOPort.h" +#include "m6502.h" +#include "debug.h" + +// PA0 SIDE0 +// PA1 !RDY +// PA2 !MOTOR +// PA3 ID 1 +// PA4 ID 2 +// PA5 POWER LED +// PA6 ACT LED +// PA7 !DISK_CHNG +// PB0 DATA IN +// PB1 DATA OUT +// PB2 CLK IN +// PB3 CLK OUT +// PB4 ATNA +// PB5 FAST SER DIR +// PB6 /WPAT +// PB7 ATN IN + +// !FLAG = !ATN IN + + +class m8520 +{ + enum Registers + { + ORA, // 0 Port A + ORB, // 1 Port B + DDRA, // 2 Data direction register for port A + DDRB, // 3 Data direction register for port B + + TALO, // 4 Timer A low + TAHI, // 5 Timer A high + TBLO, // 6 Timer B low + TBHI, // 7 Timer B high + EVENT_LSB, // 8 + EVENT_8_15, // 9 + EVENT_MSB, // 10 + + NC, // 11 No connect + SDR, // 12 Serial data register + + ICR, // 13 Interrupt control register + CRA, // 14 Control register A + CRB, // 15 Control register B + }; + + enum IR + { + IR_TA = 0x01, + IR_TB = 0x02, + IR_TOD = 0x04, + IR_SDR = 0x08, + IR_FLG = 0x10, + IR_SET = 0x80 + }; + + enum CRA_BIT + { + CRA_START = 0x01, + CRA_PBON = 0x02, + CRA_OUTPUTMODE = 0x04, + CRA_RUNMODE = 0x08, + CRA_LOAD = 0x10, + CRA_INMODE = 0x20, + CRA_SPMODE = 0x40, + CRA_TODIN = 0x80 + }; + + enum CRB_BIT + { + CRB_START = 0x01, + CRB_PBON = 0x02, + CRB_OUTPUTMODE = 0x04, + CRB_RUNMODE = 0x08, + CRB_LOAD = 0x10, + CRB_INMODE0 = 0x20, + CRB_INMODE1 = 0x40, + CRB_ALARM = 0x80 + }; + + enum TimerAMode + { + TA_MODE_PHI2, + TA_MODE_CNT_PVE + }; + + enum TimerBMode + { + TB_MODE_PHI2, + TB_MODE_CNT_PVE, + TB_MODE_TA_UNDEFLOW, + TB_MODE_TA_UNDEFLOW_CNT_PVE + }; + + enum SerialPortMode + { + SP_MODE_OUTPUT, + SP_MODE_INPUT + }; + +public: + m8520(); + + void Reset(); + void ConnectIRQ(Interrupt* irq) { this->irq = irq; } + + inline IOPort* GetPortA() { return &portA; } + inline IOPort* GetPortB() { return &portB; } + + void Execute(); + + unsigned char Read(unsigned int address); + unsigned char Peek(unsigned int address); + void Write(unsigned int address, unsigned char value); + + bool IsPCAsserted() const { return PCAsserted; } + void SetPinFLAG(bool value); // active low + void SetPinCNT(bool value); + bool GetPinCNT() const { return CNTPin; } + void SetPinSP(bool value); + bool GetPinSP() const { return SPPin; } + void SetPinTOD(bool value); + +//private: + inline unsigned char ReadPortB() + { + unsigned char ddr = portB.GetDirection(); + unsigned char value = (unsigned char)((portB.GetInput() & ~ddr) | (portB.GetOutput() & ddr)); + return value; + } + + inline void WritePortB(unsigned char value) + { + portB.SetOutput(value); + } + + inline unsigned char ReadPortA() + { + unsigned char ddr = portA.GetDirection(); + unsigned char value = (unsigned char)((portA.GetInput() & ~ddr) | (portA.GetOutput() & ddr)); + return value; + } + + inline unsigned char PeekPortA() + { + unsigned char ddr = portA.GetDirection(); + unsigned char value = (unsigned char)((portA.GetInput() & ~ddr) | (portA.GetOutput() & ddr)); + return value; + } + + inline void WritePortA(unsigned char value) + { + portA.SetOutput(value); + } + + inline unsigned char PeekPortB() + { + unsigned char ddr = portB.GetDirection(); + unsigned char value = (unsigned char)((portB.GetInput() & ~ddr) | (portB.GetOutput() & ddr)); + return value; + } + + inline void SetInterrupt(unsigned char flag) + { + if (!(ICRData & flag)) + { + ICRData |= flag; + //DEBUG_LOG("Setting IRQ %02x\r\n", flag); + OutputIRQ(); + } + } + + inline void ClearInterrupt(unsigned char flag) + { + if (ICRData & flag) + { + ICRData &= ~flag; + //DEBUG_LOG("Clearing IRQ %02x %02x\r\n", flag, ICRData); + OutputIRQ(); + } + } + + inline void OutputIRQ() + { + // Any interrupt which is enabled by the MASK register will set the IR bit(MSB) of the DATA register and bring the IRQ pin low. + if (ICRMask & ICRData & (IR_FLG | IR_SDR | IR_TOD | IR_TB | IR_TA)) + { + if ((ICRData & IR_SET) == 0) + { + ICRData |= IR_SET; + if (irq) irq->Assert(); + } + } + else + { + if (ICRData & IR_SET) + { + //DEBUG_LOG("Releasing IRQ %02x\r\n", ICRData); + ICRData &= ~IR_SET; + if (irq) irq->Release(); + } + } + } + + inline void ReloadTimerA() + { + timerACounter = timerALatch; + timerAReloaded = true; + } + + inline void ReloadTimerB() + { + timerBCounter = timerBLatch; + timerBReloaded = true; + } + + Interrupt* irq; + + IOPort portA; + IOPort portB; + + unsigned char ICRMask; + unsigned char ICRData; + + unsigned char CRARegister; + unsigned char CRBRegister; + + u32 PCAsserted; + bool FLAGPin; + bool CNTPin; + bool CNTPinOld; + bool SPPin; + bool TODPin; + + unsigned short timerACounter; + unsigned short timerALatch; + bool timerAActive; + bool timerAOutputOnPB6; + bool timerAToggle; + bool timerAOneShot; + TimerAMode timerAMode; + bool timerA50Hz; + bool ta_pb6; + bool timerAReloaded; + + unsigned short timerBCounter; + unsigned short timerBLatch; + bool timerBActive; + bool timerBOutputOnPB7; + bool timerBToggle; + bool timerBOneShot; + TimerBMode timerBMode; + bool timerBAlarm; + bool tb_pb7; + bool timerBReloaded; + + bool TODActive; + unsigned TODAlarm; + unsigned TODClock; + unsigned TODLatch; + + SerialPortMode serialPortMode; + unsigned char serialPortRegister; + unsigned char serialShiftRegister; + unsigned serialBitsShiftedSoFar; + bool serialShiftingEnabled; + //unsigned timerATimeOutCount; +}; + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index bc08b1e..82a3020 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,6 +38,7 @@ extern "C" #include "iec_commands.h" #include "diskio.h" #include "Pi1541.h" +#include "Pi1581.h" #include "FileBrowser.h" #include "ScreenLCD.h" @@ -46,10 +47,11 @@ extern "C" #include "ssd_logo.h" unsigned versionMajor = 1; -unsigned versionMinor = 12; +unsigned versionMinor = 13; // When the emulated CPU starts we execute the first million odd cycles in non-real-time (ie as fast as possible so the emulated 1541 becomes responsive to CBM-Browser asap) // During these cycles the CPU is executing the ROM self test routines (these do not need to be cycle accurate) +// ***1581*** Skip to AFCA (how many cycles is this?) #define FAST_BOOT_CYCLES 1003061 #define COLOUR_BLACK RGBA(0, 0, 0, 0xff) @@ -57,10 +59,12 @@ unsigned versionMinor = 12; #define COLOUR_RED RGBA(0xff, 0, 0, 0xff) #define COLOUR_GREEN RGBA(0, 0xff, 0, 0xff) #define COLOUR_CYAN RGBA(0, 0xff, 0xff, 0xff) +#define COLOUR_MAGENTA RGBA(0xff, 0, 0xff, 0xff) #define COLOUR_YELLOW RGBA(0xff, 0xff, 0x00, 0xff) // To exit a mounted disk image we need to watch(snoop) what the emulated CPU is doing when it executes code at some critical ROM addresses. #define SNOOP_CD_CBM 0xEA2D +#define SNOOP_CD_CBM1581 0xAEB7 #define SNOOP_CD_JIFFY_BOTH 0xFC07 #define SNOOP_CD_JIFFY_DRIVEONLY 0xEA16 static const u8 snoopBackCommand[] = { @@ -69,7 +73,14 @@ static const u8 snoopBackCommand[] = { static int snoopIndex = 0; static int snoopPC = 0; -bool emulating; // When false we are in IEC command mode level simulation. When true we are in full cycle exact emulation mode. +enum EmulatingMode +{ + IEC_COMMANDS, + EMULATING_1541, + EMULATING_1581 +}; + +EmulatingMode emulating; typedef void(*func_ptr)(); @@ -88,6 +99,7 @@ u8 s_u8Memory[0xc000]; DiskCaddy diskCaddy; Pi1541 pi1541; +Pi1581 pi1581; CEMMCDevice m_EMMC; Screen screen; ScreenLCD* screenLCD = 0; @@ -95,6 +107,11 @@ Options options; const char* fileBrowserSelectedName; u8 deviceID = 8; IEC_Commands m_IEC_Commands; +InputMappings* inputMappings; +Keyboard* keyboard; +//bool resetWhileEmulating = false; +bool selectedViaIECCommands = false; +u16 pc; unsigned int screenWidth = 1024; unsigned int screenHeight = 768; @@ -102,7 +119,6 @@ unsigned int screenHeight = 768; const char* termainalTextRed = "\E[31m"; const char* termainalTextNormal = "\E[0m"; -EXIT_TYPE exitReason = EXIT_UNKNOWN; // Hooks required for USPi library extern "C" @@ -190,139 +206,12 @@ extern "C" // Hooks for FatFs DWORD get_fattime() { return 0; } // If you have hardware RTC return a correct value here. THis can then be reflected in file modification times/dates. -/////////////////////////////////////////////////////////////////////////////////////// -// 6502 Address bus functions. -// Move here out of Pi1541 to increase performance. -/////////////////////////////////////////////////////////////////////////////////////// -// In a 1541 address decoding and chip selects are performed by a 74LS42 ONE-OF-TEN DECODER -// 74LS42 Ouputs a low to the !CS based on the four inputs provided by address bits 10-13 -// 1800 !cs2 on pin 9 -// 1c00 !cs2 on pin 7 -u8 read6502(u16 address) -{ - u8 value = 0; - if (address & 0x8000) - { - switch (address & 0xe000) // keep bits 15,14,13 - { - case 0x8000: // 0x8000-0x9fff - if (options.GetRAMBOard()) { - value = s_u8Memory[address]; // 74LS42 outputs low on pin 1 or pin 2 - break; - } - case 0xa000: // 0xa000-0xbfff - case 0xc000: // 0xc000-0xdfff - case 0xe000: // 0xe000-0xffff - value = roms.Read(address); - break; - } - } - else - { - // Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding - u16 addressLines12_11_10 = (address & 0x1c00) >> 10; - switch (addressLines12_11_10) - { - case 0: - case 1: - value = s_u8Memory[address & 0x7ff]; // 74LS42 outputs low on pin 1 or pin 2 - break; - case 6: - value = pi1541.VIA[0].Read(address); // 74LS42 outputs low on pin 7 - break; - case 7: - value = pi1541.VIA[1].Read(address); // 74LS42 outputs low on pin 9 - break; - default: - value = address >> 8; // Empty address bus - break; - } - } - return value; -} - -// Allows a mode where we have RAM at all addresses other than the ROM and the VIAs. (Maybe useful to someone?) -u8 read6502ExtraRAM(u16 address) -{ - if (address & 0x8000) - { - return roms.Read(address); - } - else - { - u16 addressLines11And12 = address & 0x1800; - if (addressLines11And12 == 0x1800) return pi1541.VIA[(address & 0x400) != 0].Read(address); // address line 10 indicates what VIA to index - return s_u8Memory[address & 0x7fff]; - } -} - -// Use for debugging (Reads VIA registers without the regular VIA read side effects) -u8 peek6502(u16 address) -{ - u8 value; - if (address & 0x8000) // address line 15 selects the ROM - { - value = roms.Read(address); - } - else - { - // Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding - u16 addressLines15_12_11_10 = (address & 0x1c00) >> 10; - addressLines15_12_11_10 |= (address & 0x8000) >> (15 - 3); - if (addressLines15_12_11_10 == 0 || addressLines15_12_11_10 == 1) value = s_u8Memory[address & 0x7ff]; // 74LS42 outputs low on pin 1 or pin 2 - else if (addressLines15_12_11_10 == 6) value = pi1541.VIA[0].Peek(address); // 74LS42 outputs low on pin 7 - else if (addressLines15_12_11_10 == 7) value = pi1541.VIA[1].Peek(address); // 74LS42 outputs low on pin 9 - else value = address >> 8; // Empty address bus - } - return value; -} - -void write6502(u16 address, const u8 value) -{ - if (address & 0x8000) - { - switch (address & 0xe000) // keep bits 15,14,13 - { - case 0x8000: // 0x8000-0x9fff - if (options.GetRAMBOard()) { - s_u8Memory[address] = value; // 74LS42 outputs low on pin 1 or pin 2 - break; - } - case 0xa000: // 0xa000-0xbfff - case 0xc000: // 0xc000-0xdfff - case 0xe000: // 0xe000-0xffff - return; - } - } - else - { - // Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding - u16 addressLines12_11_10 = (address & 0x1c00) >> 10; - switch (addressLines12_11_10) - { - case 0: - case 1: - s_u8Memory[address & 0x7ff] = value; // 74LS42 outputs low on pin 1 or pin 2 - break; - case 6: - pi1541.VIA[0].Write(address, value); // 74LS42 outputs low on pin 7 - break; - case 7: - pi1541.VIA[1].Write(address, value); // 74LS42 outputs low on pin 9 - break; - default: - break; - } - } -} - -void write6502ExtraRAM(u16 address, const u8 value) -{ - if (address & 0x8000) return; // address line 15 selects the ROM - u16 addressLines11And12 = address & 0x1800; - if (addressLines11And12 == 0) s_u8Memory[address & 0x7fff] = value; - else if (addressLines11And12 == 0x1800) pi1541.VIA[(address & 0x400) != 0].Write(address, value); // address line 10 indicates what VIA to index -} +extern u8 read6502(u16 address); +extern u8 read6502ExtraRAM(u16 address); +extern void write6502(u16 address, const u8 value); +extern void write6502ExtraRAM(u16 address, const u8 value); +extern u8 read6502_1581(u16 address); +extern void write6502_1581(u16 address, const u8 value); void InitialiseHardware() { @@ -436,6 +325,7 @@ void UpdateScreen() bool oldATN = false; bool oldDATA = false; bool oldCLOCK = false; + bool oldSRQ = false; u32 oldTrack = 0; u32 textColour = COLOUR_BLACK; @@ -444,6 +334,7 @@ void UpdateScreen() RGBA atnColour = COLOUR_YELLOW; RGBA dataColour = COLOUR_GREEN; RGBA clockColour = COLOUR_CYAN; + RGBA SRQColour = COLOUR_MAGENTA; RGBA BkColour = FileBrowser::Colour(VIC2_COLOUR_INDEX_BLUE); int height = screen.ScaleY(60); @@ -468,7 +359,21 @@ void UpdateScreen() //RPI_UpdateTouch(); //refreshUartStatusDisplay = false; - value = pi1541.drive.IsLEDOn(); + bool led = false; + bool motor = false; + + if (emulating == EMULATING_1541) + { + led = pi1541.drive.IsLEDOn(); + motor = pi1541.drive.IsMotorOn(); + } + else if (emulating == EMULATING_1581) + { + led = pi1581.IsLEDOn(); + motor = pi1581.IsMotorOn(); + } + + value = led; if (value != oldLED) { // SetACTLed(value); @@ -478,7 +383,7 @@ void UpdateScreen() //refreshUartStatusDisplay = true; } - value = pi1541.drive.IsMotorOn(); + value = motor; if (value != oldMotor) { oldMotor = value; @@ -556,29 +461,72 @@ void UpdateScreen() //refreshUartStatusDisplay = true; } + //value = IEC_Bus::GetPI_SRQ(); + //if (options.GraphIEC()) + //{ + // if (value ^ oldSRQ) + // { + // screen.DrawLineV(graphX, 0, 100, SRQColour); + // } + // else + // { + // if (value) screen.PlotPixel(graphX, 0, SRQColour); + // else screen.PlotPixel(graphX, 100, SRQColour); + // } + //} + //if (value != oldSRQ) + //{ + // oldSRQ = value; + //// snprintf(tempBuffer, tempBufferSize, "%d", value); + //// screen.PrintText(false, 41 * 8, y, tempBuffer, textColour, bgColour); + //// //refreshUartStatusDisplay = true; + //} + if (graphX++ > screenWidthM1) graphX = 0; // black vertical line ahead of graph if (options.GraphIEC()) screen.DrawLineV(graphX, top3, bottom, COLOUR_BLACK); - u32 track = pi1541.drive.Track(); - if (track != oldTrack) + u32 track; + if (emulating == EMULATING_1541) { - oldTrack = track; - snprintf(tempBuffer, tempBufferSize, "%02d.%d", (oldTrack >> 1) + 1, oldTrack & 1 ? 5 : 0); - screen.PrintText(false, 20 * 8, y, tempBuffer, textColour, bgColour); - //refreshUartStatusDisplay = true; - - if (screenLCD) + track = pi1541.drive.Track(); + if (track != oldTrack) { - screenLCD->PrintText(false, 0, 0, tempBuffer, RGBA(0xff, 0xff, 0xff, 0xff), RGBA(0xff, 0xff, 0xff, 0xff)); -// screenLCD->SetContrast(255.0/79.0*track); - screenLCD->RefreshRows(0, 1); + oldTrack = track; + snprintf(tempBuffer, tempBufferSize, "%02d.%d", (oldTrack >> 1) + 1, oldTrack & 1 ? 5 : 0); + screen.PrintText(false, 20 * 8, y, tempBuffer, textColour, bgColour); + //refreshUartStatusDisplay = true; + + if (screenLCD) + { + screenLCD->PrintText(false, 0, 0, tempBuffer, 0, RGBA(0xff, 0xff, 0xff, 0xff)); + // screenLCD->SetContrast(255.0/79.0*track); + screenLCD->RefreshRows(0, 1); + } + } - } + else + { + track = pi1581.wd177x.GetCurrentTrack(); + if (track != oldTrack) + { + oldTrack = track; + snprintf(tempBuffer, tempBufferSize, "%02d", (oldTrack) + 1); + screen.PrintText(false, 20 * 8, y, tempBuffer, textColour, bgColour); + //refreshUartStatusDisplay = true; - if (emulating) + if (screenLCD) + { + screenLCD->PrintText(false, 0, 0, tempBuffer, 0, RGBA(0xff, 0xff, 0xff, 0xff)); + // screenLCD->SetContrast(255.0/79.0*track); + screenLCD->RefreshRows(0, 1); + } + + } + } + if (emulating != IEC_COMMANDS) { //refreshUartStatusDisplay = diskCaddy.Update(); @@ -592,17 +540,27 @@ void UpdateScreen() } } -bool BeginEmulating(FileBrowser* fileBrowser, const char* filenameForIcon) +EmulatingMode BeginEmulating(FileBrowser* fileBrowser, const char* filenameForIcon) { DiskImage* diskImage = diskCaddy.SelectFirstImage(); if (diskImage) { - pi1541.drive.Insert(diskImage); - fileBrowser->DisplayDiskInfo(diskImage, filenameForIcon); - fileBrowser->ShowDeviceAndROM(); - return true; + if (diskImage->IsD81()) + { + pi1581.Insert(diskImage); + fileBrowser->DisplayDiskInfo(diskImage, filenameForIcon); + fileBrowser->ShowDeviceAndROM(); + return EMULATING_1581; + } + else + { + pi1541.drive.Insert(diskImage); + fileBrowser->DisplayDiskInfo(diskImage, filenameForIcon); + fileBrowser->ShowDeviceAndROM(); + return EMULATING_1541; + } } - return false; + return IEC_COMMANDS; } static u32* dmaSound; @@ -642,8 +600,8 @@ void GlobalSetDeviceID(u8 id) { deviceID = id; m_IEC_Commands.SetDeviceId(id); - pi1541.VIA[0].GetPortB()->SetInput(VIAPORTPINS_DEVSEL0, id & 1); - pi1541.VIA[0].GetPortB()->SetInput(VIAPORTPINS_DEVSEL1, id & 2); + pi1541.SetDeviceID(id); + pi1581.SetDeviceID(id); } void CheckAutoMountImage(EXIT_TYPE reset_reason , FileBrowser* fileBrowser) @@ -667,34 +625,381 @@ void CheckAutoMountImage(EXIT_TYPE reset_reason , FileBrowser* fileBrowser) } } -void emulator() +EXIT_TYPE Emulate1541(FileBrowser* fileBrowser) { + EXIT_TYPE exitReason = EXIT_UNKNOWN; bool oldLED = false; unsigned ctBefore = 0; unsigned ctAfter = 0; + int cycleCount = 0; + unsigned caddyIndex; + int headSoundCounter = 0; + int headSoundFreqCounter = 0; + // const int headSoundFreq = 833; // 1200Hz = 1/1200 * 10^6; + const int headSoundFreq = 1000000 / options.SoundOnGPIOFreq(); // 1200Hz = 1/1200 * 10^6; + unsigned char oldHeadDir; + int resetCount = 0; + + unsigned numberOfImages = diskCaddy.GetNumberOfImages(); + unsigned numberOfImagesMax = numberOfImages; + if (numberOfImagesMax > 10) + numberOfImagesMax = 10; + + diskCaddy.Display(); + + inputMappings->directDiskSwapRequest = 0; + // Force an update on all the buttons now before we start emulation mode. + IEC_Bus::ReadBrowseMode(); + + bool extraRAM = options.GetExtraRAM(); + DataBusReadFn dataBusRead = extraRAM ? read6502ExtraRAM : read6502; + DataBusWriteFn dataBusWrite = extraRAM ? write6502ExtraRAM : write6502; + pi1541.m6502.SetBusFunctions(dataBusRead, dataBusWrite); + + IEC_Bus::VIA = &pi1541.VIA[0]; + IEC_Bus::port = pi1541.VIA[0].GetPortB(); + pi1541.Reset(); // will call IEC_Bus::Reset(); + + ctBefore = read32(ARM_SYSTIMER_CLO); + + //resetWhileEmulating = false; + selectedViaIECCommands = false; + + while (exitReason == EXIT_UNKNOWN) + { + IEC_Bus::ReadEmulationMode1541(); + + if (pi1541.m6502.SYNC()) // About to start a new instruction. + { + pc = pi1541.m6502.GetPC(); + // See if the emulated cpu is executing CD:_ (ie back out of emulated image) + if (snoopIndex == 0 && (pc == SNOOP_CD_CBM || pc == SNOOP_CD_JIFFY_BOTH || pc == SNOOP_CD_JIFFY_DRIVEONLY)) snoopPC = pc; + + if (pc == snoopPC) + { + u8 a = pi1541.m6502.GetA(); + if (a == snoopBackCommand[snoopIndex]) + { + snoopIndex++; + if (snoopIndex == sizeof(snoopBackCommand)) + { + // Exit full emulation back to IEC commands level simulation. + snoopIndex = 0; + emulating = IEC_COMMANDS; + exitReason = EXIT_CD; + } + } + else + { + snoopIndex = 0; + snoopPC = 0; + } + } + } + + pi1541.m6502.Step(); // If the CPU reads or writes to the VIA then clk and data can change + + if (cycleCount >= FAST_BOOT_CYCLES) // cycleCount is used so we can quickly get through 1541's self test code. This will make the emulated 1541 responsive to commands asap. During this time we don't need to set outputs. + { + //To artificialy delay the outputs later into the phi2's cycle (do this on future Pis that will be faster and perhaps too fast) + //read32(ARM_SYSTIMER_CLO); //Each one of these is > 100ns + //read32(ARM_SYSTIMER_CLO); + //read32(ARM_SYSTIMER_CLO); + //IEC_Bus::RefreshOuts(); // Now output all outputs. + + IEC_Bus::OutputLED = pi1541.drive.IsLEDOn(); + if (IEC_Bus::OutputLED ^ oldLED) + { + SetACTLed(IEC_Bus::OutputLED); + oldLED = IEC_Bus::OutputLED; + } + + // Do head moving sound + unsigned char headDir = pi1541.drive.GetLastHeadDirection(); + if (headDir ^ oldHeadDir) // Need to start a new sound? + { + oldHeadDir = headDir; + if (options.SoundOnGPIO()) + { + headSoundCounter = 1000 * options.SoundOnGPIODuration(); + headSoundFreqCounter = headSoundFreq; + } + else + { + PlaySoundDMA(); + } + } + + //if (options.SoundOnGPIO() && headSoundCounter > 0) + //{ + // headSoundFreqCounter--; // Continue updating a GPIO non DMA sound. + // if (headSoundFreqCounter <= 0) + // { + // headSoundFreqCounter = headSoundFreq; + // headSoundCounter -= headSoundFreq * 8; + // IEC_Bus::OutputSound = !IEC_Bus::OutputSound; + // } + //} + + + IEC_Bus::RefreshOuts1541(); // Now output all outputs. + } + + // Other core will check the uart (as it is slow) (could enable uart irqs - will they execute on this core?) + inputMappings->CheckKeyboardEmulationMode(numberOfImages, numberOfImagesMax); + inputMappings->CheckButtonsEmulationMode(); + + bool exitEmulation = inputMappings->Exit(); + bool exitDoAutoLoad = inputMappings->AutoLoad(); + + // We have now output so HERE is where the next phi2 cycle starts. + pi1541.Update(); + + + bool reset = IEC_Bus::IsReset(); + if (reset) + resetCount++; + else + resetCount = 0; + + if ((emulating == IEC_COMMANDS) || (resetCount > 10) || exitEmulation || exitDoAutoLoad) + { + if (reset) + exitReason = EXIT_RESET; + if (exitEmulation) + exitReason = EXIT_KEYBOARD; + if (exitDoAutoLoad) + exitReason = EXIT_AUTOLOAD; + } + + if (cycleCount < FAST_BOOT_CYCLES) // cycleCount is used so we can quickly get through 1541's self test code. This will make the emulated 1541 responsive to commands asap. + { + cycleCount++; + ctAfter = read32(ARM_SYSTIMER_CLO); + } + else + { + do // Sync to the 1MHz clock + { + ctAfter = read32(ARM_SYSTIMER_CLO); + unsigned ct = ctAfter - ctBefore; + if (ct > 1) + { + // If this ever occurs then we have taken too long (ie >1us) and lost a cycle. + // Cycle accuracy is now in jeopardy. If this occurs during critical communication loops then emulation can fail! + //DEBUG_LOG("!"); + } + } while (ctAfter == ctBefore); + } + ctBefore = ctAfter; + + if (options.SoundOnGPIO() && headSoundCounter > 0) + { + headSoundFreqCounter--; // Continue updating a GPIO non DMA sound. + if (headSoundFreqCounter <= 0) + { + headSoundFreqCounter = headSoundFreq; + headSoundCounter -= headSoundFreq * 8; + IEC_Bus::OutputSound = !IEC_Bus::OutputSound; + } + } + + if (numberOfImages > 1) + { + bool nextDisk = inputMappings->NextDisk(); + bool prevDisk = inputMappings->PrevDisk(); + if (nextDisk) + { + pi1541.drive.Insert(diskCaddy.PrevDisk()); + } + else if (prevDisk) + { + pi1541.drive.Insert(diskCaddy.NextDisk()); + } + else if (inputMappings->directDiskSwapRequest != 0) + { + for (caddyIndex = 0; caddyIndex < numberOfImagesMax; ++caddyIndex) + { + if (inputMappings->directDiskSwapRequest & (1 << caddyIndex)) + { + DiskImage* diskImage = diskCaddy.SelectImage(caddyIndex); + if (diskImage && diskImage != pi1541.drive.GetDiskImage()) + { + pi1541.drive.Insert(diskImage); + break; + } + } + } + inputMappings->directDiskSwapRequest = 0; + } + } + } + return exitReason; +} + +EXIT_TYPE Emulate1581(FileBrowser* fileBrowser) +{ + EXIT_TYPE exitReason = EXIT_UNKNOWN; + bool oldLED = false; + unsigned ctBefore = 0; + unsigned ctAfter = 0; + int cycleCount = 0; + unsigned caddyIndex; + int headSoundCounter = 0; + int headSoundFreqCounter = 0; + // const int headSoundFreq = 833; // 1200Hz = 1/1200 * 10^6; + const int headSoundFreq = 1000000 / options.SoundOnGPIOFreq(); // 1200Hz = 1/1200 * 10^6; + unsigned char oldHeadDir; + int resetCount = 0; + + unsigned numberOfImages = diskCaddy.GetNumberOfImages(); + unsigned numberOfImagesMax = numberOfImages; + if (numberOfImagesMax > 10) + numberOfImagesMax = 10; + + diskCaddy.Display(); + + inputMappings->directDiskSwapRequest = 0; + // Force an update on all the buttons now before we start emulation mode. + IEC_Bus::ReadBrowseMode(); + + DataBusReadFn dataBusRead = read6502_1581; + DataBusWriteFn dataBusWrite = write6502_1581; + pi1581.m6502.SetBusFunctions(dataBusRead, dataBusWrite); + + IEC_Bus::CIA = &pi1581.CIA; + IEC_Bus::port = pi1581.CIA.GetPortB(); + pi1581.Reset(); // will call IEC_Bus::Reset(); + + ctBefore = read32(ARM_SYSTIMER_CLO); + + //resetWhileEmulating = false; + selectedViaIECCommands = false; + + while (exitReason == EXIT_UNKNOWN) + { + for (int cycle2MHz = 0; cycle2MHz < 2; ++cycle2MHz) + { + IEC_Bus::ReadEmulationMode1581(); + if (pi1581.m6502.SYNC()) // About to start a new instruction. + { + pc = pi1581.m6502.GetPC(); + // See if the emulated cpu is executing CD:_ (ie back out of emulated image) + if (snoopIndex == 0 && (pc == SNOOP_CD_CBM1581)) snoopPC = pc; + + if (pc == snoopPC) + { + u8 a = pi1581.m6502.GetA(); + if (a == snoopBackCommand[snoopIndex]) + { + snoopIndex++; + if (snoopIndex == sizeof(snoopBackCommand)) + { + // Exit full emulation back to IEC commands level simulation. + snoopIndex = 0; + emulating = IEC_COMMANDS; + exitReason = EXIT_CD; + } + } + else + { + snoopIndex = 0; + snoopPC = 0; + } + } + } + pi1581.m6502.Step(); + pi1581.Update(); + IEC_Bus::RefreshOuts1581(); // Now output all outputs. + } + + if (cycleCount >= FAST_BOOT_CYCLES) // cycleCount is used so we can quickly get through 1541's self test code. This will make the emulated 1541 responsive to commands asap. During this time we don't need to set outputs. + { + IEC_Bus::OutputLED = pi1581.IsLEDOn(); + if (IEC_Bus::OutputLED ^ oldLED) + { + SetACTLed(IEC_Bus::OutputLED); + oldLED = IEC_Bus::OutputLED; + } + + //IEC_Bus::RefreshOuts1581(); // Now output all outputs. + } + + + // Other core will check the uart (as it is slow) (could enable uart irqs - will they execute on this core?) + inputMappings->CheckKeyboardEmulationMode(numberOfImages, numberOfImagesMax); + inputMappings->CheckButtonsEmulationMode(); + + bool exitEmulation = inputMappings->Exit(); + bool exitDoAutoLoad = inputMappings->AutoLoad(); + + + bool reset = IEC_Bus::IsReset(); + if (reset) + resetCount++; + else + resetCount = 0; + + if ((emulating == IEC_COMMANDS)|| (resetCount > 10) || exitEmulation || exitDoAutoLoad) + { + if (reset) + exitReason = EXIT_RESET; + if (exitEmulation) + exitReason = EXIT_KEYBOARD; + if (exitDoAutoLoad) + exitReason = EXIT_AUTOLOAD; + } + + if (cycleCount < FAST_BOOT_CYCLES) // cycleCount is used so we can quickly get through 1541's self test code. This will make the emulated 1541 responsive to commands asap. + { + cycleCount++; + ctAfter = read32(ARM_SYSTIMER_CLO); + } + else + { + do // Sync to the 1MHz clock + { + ctAfter = read32(ARM_SYSTIMER_CLO); + unsigned ct = ctAfter - ctBefore; + if (ct > 1) + { + // If this ever occurs then we have taken too long (ie >1us) and lost a cycle. + // Cycle accuracy is now in jeopardy. If this occurs during critical communication loops then emulation can fail! + //DEBUG_LOG("!"); + } + } while (ctAfter == ctBefore); + } + ctBefore = ctAfter; + + } + return exitReason; +} + +void emulator() +{ Keyboard* keyboard = Keyboard::Instance(); - bool resetWhileEmulating = false; - bool selectedViaIECCommands = false; - InputMappings* inputMappings = InputMappings::Instance(); FileBrowser* fileBrowser; + EXIT_TYPE exitReason = EXIT_UNKNOWN; roms.lastManualSelectedROMIndex = 0; diskCaddy.SetScreen(&screen, screenLCD); - fileBrowser = new FileBrowser(&diskCaddy, &roms, &deviceID, options.DisplayPNGIcons(), &screen, screenLCD, options.ScrollHighlightRate()); + fileBrowser = new FileBrowser(inputMappings, &diskCaddy, &roms, &deviceID, options.DisplayPNGIcons(), &screen, screenLCD, options.ScrollHighlightRate()); fileBrowser->DisplayRoot(); pi1541.Initialise(); m_IEC_Commands.SetAutoBootFB128(options.AutoBootFB128()); m_IEC_Commands.Set128BootSectorName(options.Get128BootSectorName()); - emulating = false; + emulating = IEC_COMMANDS; while (1) { - if (!emulating) + if (emulating == IEC_COMMANDS) { IEC_Bus::VIA = 0; + IEC_Bus::CIA = 0; + IEC_Bus::port = 0; IEC_Bus::Reset(); // workaround for occasional oled curruption @@ -713,7 +1018,7 @@ void emulator() // else fileBrowser->RefeshDisplay(); // Just redisplay the current folder. - resetWhileEmulating = false; +// resetWhileEmulating = false; selectedViaIECCommands = false; inputMappings->Reset(); @@ -727,7 +1032,7 @@ void emulator() CheckAutoMountImage(exitReason, fileBrowser); - while (!emulating) + while (emulating == IEC_COMMANDS) { IEC_Commands::UpdateAction updateAction = m_IEC_Commands.SimulateIECUpdate(); @@ -810,7 +1115,7 @@ void emulator() } else { - while (!emulating) + while (emulating == IEC_COMMANDS) { if (keyboard->CheckChanged()) { @@ -824,216 +1129,33 @@ void emulator() } else { - int cycleCount = 0; - unsigned caddyIndex; - int headSoundCounter = 0; - int headSoundFreqCounter = 0; -// const int headSoundFreq = 833; // 1200Hz = 1/1200 * 10^6; - const int headSoundFreq = 1000000 / options.SoundOnGPIOFreq(); // 1200Hz = 1/1200 * 10^6; - unsigned char oldHeadDir; - int resetCount = 0; + if (emulating == EMULATING_1541) + exitReason = Emulate1541(fileBrowser); + else + exitReason = Emulate1581(fileBrowser); - unsigned numberOfImages = diskCaddy.GetNumberOfImages(); - unsigned numberOfImagesMax = numberOfImages; - if (numberOfImagesMax > 10) - numberOfImagesMax = 10; + DEBUG_LOG("Exited emulation\r\n"); - diskCaddy.Display(); + // Clearing the caddy now + // - will write back all changed/dirty/written to disk images now + // - TDOO: need to display the image names as they write back + // - pass in a call back function? + if (diskCaddy.Empty()) + IEC_Bus::WaitMicroSeconds(2 * 1000000); - inputMappings->directDiskSwapRequest = 0; - // Force an update on all the buttons now before we start emulation mode. - IEC_Bus::ReadBrowseMode(); + // workaround for occasional oled curruption + // if (screenLCD) + // screenLCD->ClearInit(0); - bool extraRAM = options.GetExtraRAM(); - DataBusReadFn dataBusRead = extraRAM ? read6502ExtraRAM : read6502; - DataBusWriteFn dataBusWrite = extraRAM ? write6502ExtraRAM : write6502; - pi1541.m6502.SetBusFunctions(dataBusRead, dataBusWrite); + fileBrowser->ClearSelections(); + fileBrowser->RefeshDisplay(); // Just redisplay the current folder. - IEC_Bus::VIA = &pi1541.VIA[0]; - pi1541.Reset(); // will call IEC_Bus::Reset(); + IEC_Bus::WaitUntilReset(); + //DEBUG_LOG("6502 resetting\r\n"); + emulating = IEC_COMMANDS; - ctBefore = read32(ARM_SYSTIMER_CLO); - - while (1) - { - IEC_Bus::ReadEmulationMode(); - - if (pi1541.m6502.SYNC()) // About to start a new instruction. - { - u16 pc = pi1541.m6502.GetPC(); - // See if the emulated cpu is executing CD:_ (ie back out of emulated image) - if (snoopIndex == 0 && (pc == SNOOP_CD_CBM || pc == SNOOP_CD_JIFFY_BOTH || pc == SNOOP_CD_JIFFY_DRIVEONLY)) snoopPC = pc; - - if (pc == snoopPC) - { - u8 a = pi1541.m6502.GetA(); - if (a == snoopBackCommand[snoopIndex]) - { - snoopIndex++; - if (snoopIndex == sizeof(snoopBackCommand)) - { - // Exit full emulation back to IEC commands level simulation. - snoopIndex = 0; - emulating = false; - exitReason = EXIT_CD; - } - } - else - { - snoopIndex = 0; - snoopPC = 0; - } - } - } - - pi1541.m6502.Step(); // If the CPU reads or writes to the VIA then clk and data can change - - if (cycleCount >= FAST_BOOT_CYCLES) // cycleCount is used so we can quickly get through 1541's self test code. This will make the emulated 1541 responsive to commands asap. During this time we don't need to set outputs. - { - //To artificialy delay the outputs later into the phi2's cycle (do this on future Pis that will be faster and perhaps too fast) - //read32(ARM_SYSTIMER_CLO); //Each one of these is > 100ns - //read32(ARM_SYSTIMER_CLO); - //read32(ARM_SYSTIMER_CLO); - - IEC_Bus::OutputLED = pi1541.drive.IsLEDOn(); - if (IEC_Bus::OutputLED ^ oldLED) - { - SetACTLed(IEC_Bus::OutputLED); - oldLED = IEC_Bus::OutputLED; - } - - // Do head moving sound - unsigned char headDir = pi1541.drive.GetLastHeadDirection(); - if (headDir ^ oldHeadDir) // Need to start a new sound? - { - oldHeadDir = headDir; - if (options.SoundOnGPIO()) - { - headSoundCounter = 1000 * options.SoundOnGPIODuration(); - headSoundFreqCounter = headSoundFreq; - } - else - { - PlaySoundDMA(); - } - } - - if (options.SoundOnGPIO() && headSoundCounter > 0) - { - headSoundFreqCounter--; // Continue updating a GPIO non DMA sound. - if (headSoundFreqCounter <= 0) - { - headSoundFreqCounter = headSoundFreq; - headSoundCounter -= headSoundFreq * 8; - IEC_Bus::OutputSound = !IEC_Bus::OutputSound; - } - } - - IEC_Bus::RefreshOuts(); // Now output all outputs. - } - - // Other core will check the uart (as it is slow) (could enable uart irqs - will they execute on this core?) - inputMappings->CheckKeyboardEmulationMode(numberOfImages, numberOfImagesMax); - inputMappings->CheckButtonsEmulationMode(); - - bool exitEmulation = inputMappings->Exit(); - bool exitDoAutoLoad = inputMappings->AutoLoad(); - - // We have now output so HERE is where the next phi2 cycle starts. - pi1541.Update(); - - - bool reset = IEC_Bus::IsReset(); - if (reset) - resetCount++; - else - resetCount = 0; - - if (!emulating || (resetCount > 10) || exitEmulation || exitDoAutoLoad) - { - // Clearing the caddy now - // - will write back all changed/dirty/written to disk images now - // - TDOO: need to display the image names as they write back - // - pass in a call back function? - if (diskCaddy.Empty()) - IEC_Bus::WaitMicroSeconds(2 * 1000000); - -// workaround for occasional oled curruption -// if (screenLCD) -// screenLCD->ClearInit(0); - - fileBrowser->ClearSelections(); - fileBrowser->RefeshDisplay(); // Just redisplay the current folder. - - IEC_Bus::WaitUntilReset(); - //DEBUG_LOG("6502 resetting\r\n"); - emulating = false; - resetWhileEmulating = true; - if (reset) - { - exitReason = EXIT_RESET; - if (options.GetOnResetChangeToStartingFolder() || selectedViaIECCommands) - fileBrowser->DisplayRoot(); // TO CHECK - } - if (exitEmulation) - exitReason = EXIT_KEYBOARD; - if (exitDoAutoLoad) - exitReason = EXIT_AUTOLOAD; - break; - } - - if (cycleCount < FAST_BOOT_CYCLES) // cycleCount is used so we can quickly get through 1541's self test code. This will make the emulated 1541 responsive to commands asap. - { - cycleCount++; - ctAfter = read32(ARM_SYSTIMER_CLO); - } - else - { - do // Sync to the 1MHz clock - { - ctAfter = read32(ARM_SYSTIMER_CLO); - unsigned ct = ctAfter - ctBefore; - if (ct > 1) - { - // If this ever occurs then we have taken too long (ie >1us) and lost a cycle. - // Cycle accuracy is now in jeopardy. If this occurs during critical communication loops then emulation can fail! - //DEBUG_LOG("!"); - } - } - while (ctAfter == ctBefore); - } - ctBefore = ctAfter; - - if (numberOfImages > 1) - { - bool nextDisk = inputMappings->NextDisk(); - bool prevDisk = inputMappings->PrevDisk(); - if (nextDisk) - { - pi1541.drive.Insert(diskCaddy.PrevDisk()); - } - else if (prevDisk) - { - pi1541.drive.Insert(diskCaddy.NextDisk()); - } - else if (inputMappings->directDiskSwapRequest != 0) - { - for (caddyIndex = 0; caddyIndex < numberOfImagesMax; ++caddyIndex) - { - if (inputMappings->directDiskSwapRequest & (1 << caddyIndex)) - { - DiskImage* diskImage = diskCaddy.SelectImage(caddyIndex); - if (diskImage && diskImage != pi1541.drive.GetDiskImage()) - { - pi1541.drive.Insert(diskImage); - break; - } - } - } - inputMappings->directDiskSwapRequest = 0; - } - } - } + if ((exitReason == EXIT_RESET) && (options.GetOnResetChangeToStartingFolder() || selectedViaIECCommands)) + fileBrowser->DisplayRoot(); // TO CHECK } } delete fileBrowser; @@ -1231,6 +1353,34 @@ static void CheckOptions() } } + const char* ROMName1581 = options.GetRomName1581(); + if (ROMName1581) + { + //DEBUG_LOG("%d Rom Name = %s\r\n", ROMIndex, ROMName); + if ((FR_OK == f_open(&fp, ROMName1581, FA_READ))) + { + u32 bytesRead; + + screen.Clear(COLOUR_BLACK); + snprintf(tempBuffer, tempBufferSize, "Loading ROM %s\r\n", ROMName1581); + screen.MeasureText(false, tempBuffer, &widthText, &heightText); + xpos = (widthScreen - widthText) >> 1; + ypos = (heightScreen - heightText) >> 1; + screen.PrintText(false, xpos, ypos, tempBuffer, COLOUR_WHITE, COLOUR_RED); + + SetACTLed(true); + res = f_read(&fp, roms.ROMImage1581, ROMs::ROM1581_SIZE, &bytesRead); + SetACTLed(false); + if (res == FR_OK && bytesRead == ROMs::ROM1581_SIZE) + { + strncpy(roms.ROMName1581, ROMName1581, 255); + roms.UpdateLongestRomNameLen(strlen(roms.ROMName1581)); + } + f_close(&fp); + //DEBUG_LOG("Read ROM %s from options\r\n", ROMName); + } + } + int ROMIndex; for (ROMIndex = ROMs::MAX_ROMS - 1; ROMIndex >= 0; --ROMIndex) @@ -1291,7 +1441,6 @@ static void CheckOptions() } - InputMappings* inputMappings = InputMappings::Instance(); if (options.GetButtonEnter() ) inputMappings->INPUT_BUTTON_ENTER = options.GetButtonEnter()-1; if (options.GetButtonUp() ) @@ -1368,10 +1517,11 @@ extern "C" //else // DEBUG_LOG("Mouse found\r\n"); - Keyboard::Instance(); - InputMappings::Instance(); + keyboard = new Keyboard(); + inputMappings = new InputMappings(); //USPiMouseRegisterStatusHandler(MouseHandler); + CheckOptions(); IEC_Bus::SetSplitIECLines(options.SplitIECLines()); diff --git a/src/options.cpp b/src/options.cpp index 6ebbf6d..f0671eb 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -170,6 +170,7 @@ Options::Options(void) ROMNameSlot6[0] = 0; ROMNameSlot7[0] = 0; ROMNameSlot8[0] = 0; + ROMName1581[0] = 0; } #define ELSE_CHECK_DECIMAL_OPTION(Name) \ @@ -265,6 +266,10 @@ void Options::Process(char* buffer) else if (strcasecmp(pValue, "sh1106_128x64") == 0) i2cLcdModel = LCD_1106_128x64; } + else if ((strcasecmp(pOption, "ROM1581") == 0)) + { + strncpy(ROMName1581, pValue, 255); + } else if ((strcasecmp(pOption, "ROM") == 0) || (strcasecmp(pOption, "ROM1") == 0)) { strncpy(ROMName, pValue, 255); @@ -344,3 +349,9 @@ const char* Options::GetRomName(int index) const } return ROMName; } + +const char* Options::GetRomName1581() const +{ + return ROMName1581; +} + diff --git a/src/options.h b/src/options.h index c15efb4..bfe6806 100644 --- a/src/options.h +++ b/src/options.h @@ -51,6 +51,7 @@ public: inline const char* GetAutoMountImageName() const { return autoMountImageName; } inline const char* GetRomFontName() const { return ROMFontName; } const char* GetRomName(int index) const; + const char* GetRomName1581() const; inline const char* GetStarFileName() const { return starFileName; } inline unsigned int GetExtraRAM() const { return extraRAM; } inline unsigned int GetRAMBOard() const { return RAMBOard; } @@ -163,5 +164,6 @@ private: char ROMNameSlot6[256]; char ROMNameSlot7[256]; char ROMNameSlot8[256]; + char ROMName1581[256]; }; #endif diff --git a/src/wd177x.cpp b/src/wd177x.cpp new file mode 100644 index 0000000..85cce81 --- /dev/null +++ b/src/wd177x.cpp @@ -0,0 +1,1402 @@ +// burst +// - graph SRQ +// - option to display it +// test +// - read track (how?) +// - write protect +// clean up +// - cpu accesses inside the cpps +// snoop +// format +// clear caddy if different image selected + + + + +// Pi1541 - A Commodore 1541 disk drive emulator +// Copyright(C) 2018 Stephen White +// +// This file is part of Pi1541. +// +// Pi1541 is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Pi1541 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Pi1541. If not, see . + +#include "wd177x.h" +#include "debug.h" + +#include "Pi1581.h" + +extern Pi1581 pi1581; + +extern bool bLoggingCYCs; +int stepcount = 0; +int racount = 0; + +// Clocks +// Master 16Mhz +// into 74ls93 +// QB - 16Mhz / 2 = 8MHz (177x) +// QD - 16Mhz / 8 = 2MHz (6502) +// CS +// 8520 +// $4000 +// +// 1770 +// $6000 +// +// ROM +// $8000 +// +// RAM +// 0-$1fff + +// Disk spins at 300rpm = 5rps so to calculate how many 8Mhz cycles one rotation takes;- +// 8000000 / 5 = 1600000; + +// With 6122 bytes per head track +// 1600000 / 6122 = 261.3525 cycles per byte +// V is 64 so scale is 4.078 +// + +static const unsigned int CYCLES_8Mhz_PER_ROTATION = 1600000; +static const unsigned int CYCLES_8Mhz_PER_BYTE = 261; + +#define Mhz 8 + +WD177x::WD177x() +{ + diskImage = 0; + Reset(); +} + +void WD177x::Reset() +{ + inactiveRotationCount = 0; + + externalMotorAsserted = false; + writeProtectAsserted = false; + + //trackRegister; + //sectorRegister; + statusRegister = 0; + //dataRegister; + commandRegisterPrevious = 0; + commandRegister = 0; + + motorSpinCount = 0; + + currentTrack = 0; + stepDirection = 1; + + commandStage = 0; + + headDataOffset = 0; + + byteRotationCycle = 0; + + delayTimer = 0; +} + +bool WD177x::GetWPRTPin() const // active low +{ + // This input is sampled whenever a Write Command is received A logic low on this line will prevent any Write Command from executing(internal pull up) + bool writeProtect = true; + + if (diskImage) + writeProtect = diskImage->GetReadOnly(); + + return !writeProtect; // active low +} + +void WD177x::SetWPRTPin(bool value) // active low +{ +// writeProtectAsserted = (value == false); +} + +void WD177x::AssertIRQ() +{ + if (irq) irq->Assert(); +} + +void WD177x::ReleaseIRQ() +{ + if (irq) irq->Release(); +} + +void WD177x::UpdateCommandType1() +{ + //DEBUG_LOG("UCT1 %d\r\n", commandStage); + + // For Type I commands, this bit is high during the index pulse that occurs once per disk rotation low otherwise. + if (!GetIPPin()) // active low + statusRegister |= INDEX_DATAREQUEST; + else + statusRegister &= ~INDEX_DATAREQUEST; + + // For Type I commands, this bit is 0 if the mechanism is at track zero and 1 if the head is not. + if (!GetTR00Pin()) // active low + statusRegister &= ~TRACKZERO_LOSTDATA; + else + statusRegister |= TRACKZERO_LOSTDATA; + + switch (commandStage) + { + case 0: + // slight usec delay before BUSY + delayTimer = 5 * Mhz; + commandStage++; + break; + case 1: + //DEBUG_LOG("%d %02x\r\n", delayTimer, statusRegister); + delayTimer--; + if (delayTimer < 0) + commandStage++; + break; + case 2: // Wait for disk to spin up + statusRegister = BUSY; + //DEBUG_LOG("BUSY = %02x\r\n", statusRegister); + + if (commandValue & COMMANDBIT_H) + { + // In a 1581 the 8520 drives the motor + //if (motorSpinCount) + // motorSpinCount--; // Need to wait 6 revoiutions before executing the command. + + //if (motorSpinCount == 0) + //{ + statusRegister |= SPINUP_DATAMARK; + commandStage++; + //} + } + else + { + commandStage++; + } + + // r (Step Time) - This bit pair determines the time between track steps according to the following table: + // r1 r0 1770 1772 + // -- -- ---- ---- + // 0 0 6ms 6ms + // 0 1 12ms 12ms + // 1 0 20ms 2ms + // 1 1 30ms 3ms + switch (commandValue & 3) + { + case 0: + delayTimer = 6000 * Mhz; + break; + case 1: + delayTimer = 12000 * Mhz; + break; + case 2: + delayTimer = 2000 * Mhz; + break; + case 3: + delayTimer = 3000 * Mhz; + break; + } + break; + case 3: + //DEBUG_LOG("%d %02x\r\n", delayTimer, statusRegister); + delayTimer--; + if (delayTimer < 0) + commandStage++; + break; + case 4: + switch (command) + { + case RESTORE: + // For now teleport + trackRegister = 0; + currentTrack = 0; + + CommandComplete(); + break; + case SEEK: + // For now teleport + currentTrack = dataRegister; + trackRegister = dataRegister; + + CommandComplete(); + break; + case STEP: + case STEP + 1: + case STEP_IN: + case STEP_IN + 1: + case STEP_OUT: + case STEP_OUT + 1: + currentTrack += stepDirection; + if (currentTrack < 0) + currentTrack = 0; + else if (currentTrack > 79) + currentTrack = 79; + + if (commandValue & COMMANDBIT_U) + { + trackRegister = currentTrack; + } + CommandComplete(); + break; + default: + CommandComplete(); + break; + } + break; + //CommandComplete(); + } +} + +unsigned char sb[512 * 50]; +int sbo = 0; + +void WD177x::UpdateCommandType2() +{ + switch (command) + { + case READ_SECTOR: + switch (commandStage) + { + case 0: + // slight usec delay before BUSY + // THERE IS NO DOCUMETED FIGURE IN THE DATASHEET - just guessing (as the 1581 ROM is polling the BUSY to go high and needs this delay) + delayTimer = 5 * Mhz; + commandStage++; + break; + case 1: + //DEBUG_LOG("%d %02x\r\n", delayTimer, statusRegister); + delayTimer--; + if (delayTimer < 0) + commandStage++; + break; + case 2: // Wait for disk to spin up + statusRegister = BUSY; + lastByteWasASync = false; + + if (settleCycleDelay) + { + settleCycleDelay--; + } + else + { + rotationCountForSeekError = 0; + commandStage++; + } + break; + case 3: + { + // When an ID field is encountered that has the correct track number, correct sector number, and correct CRC, the data field is presented to the computer. + // The Data Address Mark of the data field is found with 30 bytes in single density and 43 bytes in double density of the last ID field CRC byte. + // If not, the ID field is searched for and verified again followed by the Data Address Mark search. + // If, alter five revolutions the DAM is not found, the Record Not Found Status Bit is set and the operation is terminated. + // When the first character or byte of the data field is shifted through the DSR, it is transferred to the DR, and DRQ is generated. + // When the next byte is accumulated in the DSR, it is transferred to the DR and another DRO is generated. + // If the computer has not read the previous contents of the DR before a new character is transferred that character is lost and the Lost Data Status Bit is set. + //UpdateReadWriteByte(); + + unsigned char byteRead; + bool syncByte; + if (ReadByte(byteRead, syncByte)) + { + switch (readAddressState) + { + case SEARCHING_FOR_NEXT_ID: + + if (CheckForSeekError()) + { + statusRegister |= SEEK_ERROR; + CommandComplete(); + } + else + { + // byte after the last sync needs to be 0xfe + if (!syncByte) + { + if (lastByteWasASync && byteRead == 0xfe) + { + readAddressState = WAIT_FOR_TRACK; + } + lastByteWasASync = false; + } + else + { + lastByteWasASync = true; + } + } + break; + case WAIT_FOR_TRACK: + if (byteRead == trackRegister) + readAddressState = WAIT_FOR_SIDE; + else + readAddressState = SEARCHING_FOR_NEXT_ID; + break; + case WAIT_FOR_SIDE: + readAddressState = WAIT_FOR_SECTOR; + break; + case WAIT_FOR_SECTOR: + if (byteRead == sectorRegister) + readAddressState = SEARCHING_FOR_NEXT_DATAID; + else + readAddressState = SEARCHING_FOR_NEXT_ID; + break; + case SEARCHING_FOR_NEXT_DATAID: + if (!syncByte) + { + if (lastByteWasASync && byteRead == 0xfb) + { + sectorByteIndex = 0; + readAddressState = READING_SECTOR; + } + lastByteWasASync = false; + } + else + { + lastByteWasASync = true; + } + break; + case READING_SECTOR: + SetDataRegister(byteRead); + sectorByteIndex++; + if (sectorByteIndex == D81_SECTOR_LENGTH) + { + if (commandValue & COMMANDBIT_M) + { + if (sectorRegister == 10) + { + CommandComplete(); + } + else + { + sectorRegister++; + readAddressState = SEARCHING_FOR_NEXT_ID; + } + } + else + { + CommandComplete(); + } + } + break; + } + } + } + break; + } + break; + + case WRITE_SECTOR: + switch (commandStage) + { + case 0: + // slight usec delay before BUSY + // THERE IS NO DOCUMETED FIGURE IN THE DATASHEET - just guessing (as the 1581 ROM is polling the BUSY to go high and needs this delay) + delayTimer = 5 * Mhz; + commandStage++; + break; + case 1: + //DEBUG_LOG("%d %02x\r\n", delayTimer, statusRegister); + delayTimer--; + if (delayTimer < 0) + commandStage++; + break; + case 2: // Wait for disk to spin up + statusRegister = BUSY; + lastByteWasASync = false; + + if (settleCycleDelay) + { + settleCycleDelay--; + } + else + { + if (writeProtectAsserted) + { + DEBUG_LOG("WP\r\n"); + statusRegister |= WRITE_PROTECT; + CommandComplete(); + } + else + { + rotationCountForSeekError = 0; + commandStage++; + } + } + break; + case 3: + { + unsigned char byteRead; + bool syncByte; + if (ReadByte(byteRead, syncByte)) + { + switch (readAddressState) + { + case SEARCHING_FOR_NEXT_ID: + if (CheckForSeekError()) + { + statusRegister |= SEEK_ERROR; + CommandComplete(); + } + else + { + // byte after the last sync needs to be 0xfe + if (!syncByte) + { + if (lastByteWasASync && byteRead == 0xfe) + { + readAddressState = WAIT_FOR_TRACK; + } + lastByteWasASync = false; + } + else + { + lastByteWasASync = true; + } + } + break; + case WAIT_FOR_TRACK: + if (byteRead == trackRegister) + readAddressState = WAIT_FOR_SIDE; + else + readAddressState = SEARCHING_FOR_NEXT_ID; + break; + case WAIT_FOR_SIDE: + readAddressState = WAIT_FOR_SECTOR; + break; + case WAIT_FOR_SECTOR: + if (byteRead == sectorRegister) + readAddressState = SEARCHING_FOR_NEXT_DATAID; + else + readAddressState = SEARCHING_FOR_NEXT_ID; + break; + case SEARCHING_FOR_NEXT_DATAID: + if (!syncByte) + { + if (lastByteWasASync) + { + if (commandValue & COMMANDBIT_A) + dataAddressMark = 0xf8; + else + dataAddressMark = 0xfb; + + if (diskImage) + diskImage->SetD81Byte(currentTrack, currentSide, headDataOffset, dataAddressMark); + + GetDataRegister(); + + sectorByteIndex = 0; + readAddressState = WRITING_SECTOR; + } + lastByteWasASync = false; + } + else + { + lastByteWasASync = true; + } + break; + case WRITING_SECTOR: + { + unsigned char data = GetDataRegister(); + if (sectorByteIndex == 0) + { + crc = 0xffff; + DiskImage::CRC(crc, 0xa1); + DiskImage::CRC(crc, 0xa1); + DiskImage::CRC(crc, 0xa1); + DiskImage::CRC(crc, dataAddressMark); + } + DiskImage::CRC(crc, data); + + sb[sbo++] = data; + + //DEBUG_LOG("%d %02x\r\n", sectorByteIndex, data); + if (diskImage) + diskImage->SetD81Byte(currentTrack, currentSide, headDataOffset, data); + + sectorByteIndex++; + if (sectorByteIndex == D81_SECTOR_LENGTH) + { + readAddressState = WAIT_FOR_CRC1; + } + } + break; + case WAIT_FOR_CRC1: + if (diskImage) + diskImage->SetD81Byte(currentTrack, currentSide, headDataOffset, (unsigned char)(crc >> 8)); + readAddressState = WAIT_FOR_CRC2; + break; + case WAIT_FOR_CRC2: + if (diskImage) + diskImage->SetD81Byte(currentTrack, currentSide, headDataOffset, (unsigned char)(crc & 0xff)); + + if (commandValue & COMMANDBIT_M) + { + if (sectorRegister == 10) + { + CommandComplete(); + } + else + { + sectorRegister++; + readAddressState = SEARCHING_FOR_NEXT_ID; + } + } + else + { + CommandComplete(); + + for (int i = 0; i < sbo; ++i) + { + DEBUG_LOG("%d %02x\r\n", i, sb[i]); + } + sbo = 0; + } + break; + } + } + } + break; + } + break; + default: + CommandComplete(); + break; + } + +} + +void WD177x::UpdateCommandType3() +{ + switch (command) + { + case READ_ADDRESS: + { + switch (commandStage) + { + case 0: + // slight usec delay before BUSY + // THERE IS NO DOCUMETED FIGURE IN THE DATASHEET - just guessing (as the 1581 ROM is polling the BUSY to go high and needs this delay) + delayTimer = 5 * Mhz; + commandStage++; + break; + case 1: + //DEBUG_LOG("%d %02x\r\n", delayTimer, statusRegister); + delayTimer--; + if (delayTimer < 0) + commandStage++; + break; + case 2: // Wait for disk to spin up + statusRegister = BUSY; + lastByteWasASync = false; + + if (settleCycleDelay) + { + settleCycleDelay--; + } + else + { + readAddressState = SEARCHING_FOR_NEXT_ID; + commandStage++; + } + break; + case 3: + { + unsigned char byteRead; + + bool syncByte; + if (ReadByte(byteRead, syncByte)) + { + switch (readAddressState) + { + case SEARCHING_FOR_NEXT_ID: + // byte after the last sync needs to be 0xfe + if (!syncByte) + { + if (lastByteWasASync && byteRead == 0xfe) + { + //DEBUG_LOG("RA found ID\r\n"); + readAddressState = WAIT_FOR_TRACK; + } + lastByteWasASync = false; + } + else + { + lastByteWasASync = true; + } + break; + case WAIT_FOR_TRACK: + SetDataRegister(byteRead); + readAddressState = WAIT_FOR_SIDE; + break; + case WAIT_FOR_SIDE: + SetDataRegister(byteRead); + readAddressState = WAIT_FOR_SECTOR; + break; + case WAIT_FOR_SECTOR: + sectorFromHeader = byteRead; + SetDataRegister(byteRead); + readAddressState = WAIT_FOR_SECTOR_LENGTH; + break; + case WAIT_FOR_SECTOR_LENGTH: + SetDataRegister(byteRead); + readAddressState = WAIT_FOR_CRC1; + break; + case WAIT_FOR_CRC1: + SetDataRegister(byteRead); + readAddressState = WAIT_FOR_CRC2; + break; + case WAIT_FOR_CRC2: + //DEBUG_LOG("%02x\r\n", byteRead); + SetDataRegister(byteRead); + sectorRegister = sectorFromHeader; + CommandComplete(); + break; + } + } + } + } + } + break; + + case READ_TRACK: + switch (commandStage) + { + case 0: + // slight usec delay before BUSY + // THERE IS NO DOCUMETED FIGURE IN THE DATASHEET - just guessing (as the 1581 ROM is polling the BUSY to go high and needs this delay) + delayTimer = 5 * Mhz; + commandStage++; + break; + case 1: + //DEBUG_LOG("%d %02x\r\n", delayTimer, statusRegister); + delayTimer--; + if (delayTimer < 0) + commandStage++; + break; + case 2: // Wait for disk to spin up + statusRegister = BUSY; + lastByteWasASync = false; + + if (settleCycleDelay) + { + settleCycleDelay--; + } + else + { + if (diskImage) + commandStage++; + else + CommandComplete(); + } + break; + case 3: // Wait for index hole + if (rotationCycle == 0) + { + sectorByteIndex = 0; + headDataOffset = 0; + byteRotationCycle = CYCLES_8Mhz_PER_BYTE; + commandStage++; + } + break; + case 4: + { + unsigned char byteRead; + bool syncByte; + if (ReadByte(byteRead, syncByte)) + { + SetDataRegister(byteRead); + sectorByteIndex++; + + if (sectorByteIndex == diskImage->TrackLength(currentTrack)) + CommandComplete(); + } + } + } + break; + + case WRITE_TRACK: + switch (commandStage) + { + case 0: + // slight usec delay before BUSY + // THERE IS NO DOCUMETED FIGURE IN THE DATASHEET - just guessing (as the 1581 ROM is polling the BUSY to go high and needs this delay) + delayTimer = 5 * Mhz; + commandStage++; + break; + case 1: + //DEBUG_LOG("%d %02x\r\n", delayTimer, statusRegister); + delayTimer--; + if (delayTimer < 0) + commandStage++; + break; + case 2: // Wait for disk to spin up + statusRegister = BUSY; + lastByteWasASync = false; + + if (settleCycleDelay) + { + settleCycleDelay--; + } + else + { + if (writeProtectAsserted) + { + statusRegister |= WRITE_PROTECT; + CommandComplete(); + } + else + { + rotationCountForSeekError = 0; + commandStage++; + } + } + break; + case 3: + { + // Writing starts with the leading edge of the first encountered Index Pulse and continues until the next index Pulse, at which time the interrupt is activated. + // The Data Request is activated immediately upon receiving the command, but writing does not start until after the first byte is loaded into the Data Register. + // If the DR is not loaded within three byte times, the operation is terminated making the device Not Busy, the Lost Data Status Bit is set, and the interrupt is activated. + + // If a byte is not present in the DR when needed, a byte of zeroes is substituted. + + // This sequence continues from one index Pulse to the next. + // Normally whatever data pattern appears in the Data Register is written on the disk with a normal clock pattern. + // However, if the WD177X-00 detects a data pattern of F5 through FE in the Data Register, this is interpreted as Data Address Marks with missing ciocks or CRC generation. + + // The CRC generator is initialized when any data byte from F8 to FE is transferred from the DR to the DSR in FM or by receipt of F5 in MFM. + // An F7 pattern generates two CRC characters in FM or MFM. + // As a consequence, the patterns F5 through FE do not appear in the gaps, data field, or ID ttetds. + // Also, CRC’s are generated by an F7 pattern. + + // Disks are formatted in IBM 3 ? 40 or System 34 formats with sector lengths of 128. 256, 512, or 1024 bytes. + + CommandComplete(); + } + } + break; + default: + CommandComplete(); + break; + } +} + +void WD177x::UpdateCommandType4() +{ + CommandComplete(); +} + +void WD177x::CommandComplete() +{ + // Add a small usec delay before we drop the BUSY flag. + // This gives the CPU time to query the dataRegister before we signal the command complete. + // THERE IS NO DOCUMETED FIGURE IN THE DATASHEET - just guessing. + // When the 1581 ROM issues a FORCE_INTERRUPT it will waste time calling AD2F a few times + delayTimer = 30 * Mhz; + + commandType = 0; + //DEBUG_LOG("CC %02x\r\n", statusRegister); +} + +bool WD177x::ReadByte(unsigned char& byte, bool& syncByte) +{ + bool byteRead = false; + + byteRotationCycle++; + + if (byteRotationCycle >= CYCLES_8Mhz_PER_BYTE) + { + byteRotationCycle = 0; + + if (diskImage) + { + unsigned int trackLength = diskImage->TrackLength((unsigned int)currentTrack); + + headDataOffset++; + if (headDataOffset > trackLength) + { + headDataOffset = 0; + rotationCountForSeekError++; + } + + currentByteRead = diskImage->GetD81Byte(currentTrack, currentSide, headDataOffset); + syncByte = diskImage->IsD81ByteASync(currentTrack, currentSide, headDataOffset); + + byte = currentByteRead; + byteRead = true; + } + //else + //{ + // DEBUG_LOG("NO disk!\r\n"); + //} + } + + return byteRead; +} + +//void WD177x::UpdateReadWriteByte() +//{ +// byteRotationCycle++; +// +// if (byteRotationCycle >= CYCLES_8Mhz_PER_BYTE) +// { +// byteRotationCycle = 0; +// +// if (diskImage) +// { +// unsigned int trackLength = diskImage->TrackLength((unsigned int)currentTrack); +// +// headDataOffset++; +// if (headDataOffset > trackLength) +// headDataOffset = 0; +// +// // Watch/trace V fdd.c line 557 +// unsigned headIndex = 0; +// currentByteRead = diskImage->GetD81Byte(currentTrack, headIndex, headDataOffset); +// +// // if reading conditions have been met eg correct track number, correct sector number, and correct CRC this can be transfered into the dataRegister +// //dataRegister = currentByteRead; +// +// // INDEX_DATAREQUEST +// // For Type II and III commands, high signals the CPU to handle the data register in order to maintain a continuous flow of data. +// // High when the data register is full during a read or when the data register is empty during a write. +// +// // When a byte is ready +// //sectorRegister |= INDEX_DATAREQUEST; +// } +// } +//} + +// Update for a single cycle +void WD177x::Execute() +{ + // The Direction signal is active high when stepping in and low when stepping out. + // A 4 uSec(MFM) or 8 uSec(FM) pulse is provided as an output to the drive. + // The Direction signal is valid 24 uSec before the first stepping pulse is generated. + // Alter the last directional step an additional 30 msec of head settling time takes place if the Verify flag is set in Type I Commands. + // There is also a 30 msec head settling time if the E flag is set In any Type II or III Command. + + // When a Seek, Step or Restore Command is executed, an optional verification of Read / Write head position can be performed by setting bit 2 (V = 1) in the command word to a logic 1. + // The verification operation begins at the end of the 30 msec settling time. + // The track number from the first encountered ID Field is compared against the contents of the Track Register. + // If the track numbers compare and the ID Field CRC is correct, the verify operation is complete and an INTRQ is generated with no errors. + // If there is a match but not a valid CRC, the CRC error status bit is set (Status Bit 3), and the next encountered ID Field is read from the disk for the verification operation. + + // The WD177x finds an ID Field with correct track number and correct CRC within 5 revolutions of the media, or the seek error is set and an INTRQ is generated. If V = 0, no verification is performed. + + + // Rotation + // 1600000 / 6122 = 261.3525 cycles per byte + // When the first character or byte of the data field is shifted through the DSR, it is transferred to the DR, and DRQ is generated.When the next byte is accumulated in the DSR, it is transferred to the DR and another DRO is generated. + // If the computer has not read the previous contents of the DR before a new character is transferred that character is lost and the Lost Data Status Bit is set. + // + // At the end of the Read operation, the type of Data Address Mark encountered in the data field is recorded in the Status Register(Bit 5) as shown : + // 1 Deleted data mark + // 0 Data mark + + rotationCycle++; + if (rotationCycle == CYCLES_8Mhz_PER_ROTATION) + { + rotationCycle = 0; + if (commandType == 0) + { + // In a 1581 the 8520 drives the motor + //// If the 177x is idle for 9 consecutive disk revolutions, it turns off the drive motor. In a 1581 the CIA will do this. + //inactiveRotationCount++; + //if (inactiveRotationCount > 9) + //{ + // SpinDown(); + //} + } + if (commandRegisterPrevious & 4) // I2 + { + AssertIRQ(); + } + } + + switch (commandType) + { + case 0: + if (delayTimer) + { + delayTimer--; + if (delayTimer == 0) + statusRegister &= ~BUSY; + } + break; + case 1: + UpdateCommandType1(); + break; + case 2: + UpdateCommandType2(); + break; + case 3: + UpdateCommandType3(); + break; + case 4: + UpdateCommandType4(); + break; + } +} + +unsigned char WD177x::Read(unsigned int address) +{ + unsigned char value = 0; + + //The address bits A1 and A0, combined with the signals R/W, are interpreted as selecting the following registers: + //A1 A0 Read Write + // 0 0 Status Register Command Register + // 0 1 Track Register Track Register + // 1 0 Sector Register Sector Register + // 1 1 Data Register Data Register + + // Alter any register is written to, the same register can not be read from until 16 usec in MFM or 32 usec in FM have elapsed. + + switch (address & 3) + { + case STATUS: + // a CPU read of the Status Register clears the 177x's interrupt output. + value = statusRegister; + + ReleaseIRQ(); + break; + case TRACK: + value = trackRegister; + break; + case SECTOR: + value = sectorRegister; + break; + case DATA: + value = dataRegister; + // When the Data Register is read the DRQ bit in the Status Register and the DRQ line are automatically reset. + statusRegister &= ~INDEX_DATAREQUEST; + break; + } + + return value; +} + +unsigned char WD177x::Peek(unsigned int address) +{ + unsigned char value = 0; + + switch (address & 3) + { + case COMMAND: + value = statusRegister; + break; + case TRACK: + value = trackRegister; + break; + case SECTOR: + value = sectorRegister; + break; + case DATA: + value = dataRegister; + break; + } + + return value; +} + +void WD177x::Write(unsigned int address, unsigned char value) +{ + //The address bits A1 and A0, combined with the signals R/W, are interpreted as selecting the following registers: + //A1 A0 Read Write + // 0 0 Status Register Command Register + // 0 1 Track Register Track Register + // 1 0 Sector Register Sector Register + // 1 1 Data Register Data Register + + // Alter any register is written to, the same register can not be read from until 16 usec in MFM or 32 usec in FM have elapsed. + + inactiveRotationCount = 0; + + switch (address & 3) + { + case COMMAND: + { + // INTRQ is reset by either reading the Status Register or by loading the Command Register with a new command. + ReleaseIRQ(); + + command = value >> 4; + commandValue = value; + // When the 177x is busy, it ignores CPU writes to this register UNLESS the new command is a force interrupt. + + // If the 177x receives a Force Interrupt command while it is executing another command, the FDDC will clear the + // Busy bit and leave all other Status bits unchanged. + // If the 177x receives a Force Interrupt command while it is not executing a command, the 177x will update the + // entire Status Register as though it had just executed a Type I command. + + if ((statusRegister & BUSY) && (command != FORCE_INTERRUPT)) + { + DEBUG_LOG("Command rejected BUSY\n"); + } + else + { + // Type I Commands + // Command Bit 7 B6 B5 B4 B3 B2 B1 Bit 0 + // -------- ----- -- -- -- -- -- -- ----- + // Restore 0 0 0 0 h V r1 r0 + // Seek 0 0 0 1 h V r1 r0 + // Step 0 0 1 u h V r1 r0 + // Step in 0 1 0 u h V r1 r0 + // Step out 0 1 1 u h V r1 r0 + + // u (Update Track Register) - If this flag is set, the 177x will update the track register after executing the command. + // h (Motor On) - If the value of this bit is 1, the controller will disable the motor spinup sequence. + // If the h Flag is not set and the MO signal is low when a command is received, ir forces MO to a logic 1 and waits 6 revoiutions before executing the command. + // Otherwise, if the motor is off, the chip will turn the motor on and wait 6 revolutions before executing the command. + // At 300 RPM, the 6 - revolution wait guarantees a one - second start time. If the 177x is idle for 9 consecutive disk revolutions, it turns off the drive motor. + // If the 177x receives a command while the motor is on, the controller executes the command immediately. + + // r (Step Time) - This bit pair determines the time between track steps according to the following table: + // r1 r0 1770 1772 + // -- -- ---- ---- + // 0 0 6ms 6ms + // 0 1 12ms 12ms + // 1 0 20ms 2ms + // 1 1 30ms 3ms + + + // Type II Commands + // Command Bit 7 B6 B5 B4 B3 B2 B1 Bit 0 + // ------------ ----- -- -- -- -- -- -- ----- + // Read Sector 1 0 0 m h E 0 0 + // Write Sector 1 0 1 m h E P a0 + + // m (Multiple Sectors) - If this bit = 0, the 177x reads or writes ("accesses") only one sector. + // If this bit = 1, the 177x sequentially accesses sectors up to and including the last sector on the track. + // A multiple-sector command will end prematurely when the CPU loads a Force Interrupt command into the Command Register. + // E(Settling Delay) - If this flag is set, the head settles before command execution. + // The settling time is 15ms for the 1772 and 30ms for the 1770. + // P (Write Precompensation) - On the 1770-02 and 1772-00, a 0 value in this bit enables automatic write precompensation. + // The FDDC delays or advances the write bit stream by one-eighth of a cycle according to the following table. + // Previous Current bit Next bit + // bits sent sending to be sent Precompensation + // --------- ----------- ---------- --------------- + // x 1 1 0 Early + // x 0 1 1 Late + // 0 0 0 1 Early + // 1 0 0 0 Late + // + // Programmers typically enable precompensation on the innermost tracks, where bit shifts usually occur and bit density is maximal. + // A 1 value for this flag disables write precompensation. + // a0(Data Address Mark) - If this bit is 0, the 177x will write a normal data mark (0xfb). + // If this bit is 1, the 177x will write a deleted data mark (0xf8). + + // Type III commands are Read Address, Read Track, and Write Track. + // Command Bit 7 B6 B5 B4 B3 B2 B1 Bit 0 + // ------------ ----- -- -- -- -- -- -- ----- + // Read Address 1 1 0 0 h E 0 0 + // Read Track 1 1 1 0 h E 0 0 + // Write Track 1 1 1 1 h E P 0 + + // Read Address: + // The 177x reads the next ID field it finds, then sends the CPU the following six bytes via the Data Register: + // Byte # Meaning | Sector length code Sector + // length + // ------ ------------------ | ------------------------------- + // 1 Track | 0 128 + // 2 Side | 1 256 + // 3 Sector | 2 512 + // 4 Sector length code | 3 1024 + // 5 CRC byte 1 | + // 6 CRC byte 2 | + + bool spinningUp = false; + + commandStage = 0; + commandRegisterPrevious = commandRegister; + commandRegister = value; + + //DEBUG_LOG("Command %02x\r\n", value); + + // In a 1581 the 8520 drives the motor + //if ((command != FORCE_INTERRUPT) && ((value & 8) == 0)) + // spinningUp = SpinUp(); + + statusRegister = 0; + + switch (command) + { + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Type I Commands + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + case RESTORE: // seek to track 0 + // Upon receipt of this command, the Track 00 input is sampled. If TR00 is active low indicating the Read/write head is positioned over track 0, + // the Track Register is Loaded with zeroes and an interrupt is generated. + // If TR00 is not active low, stepping pulses at a rate specified by the r1 r0 field are issued until the TR00 input is activated. + + // When at track 0, the Track Register is loaded with zeroes and an interrupt is generated. + // If the TR00 input does not go active low alter 255 stepping pulses, the WD177x terminates operation, interrupts, and sets the Seek Error status bit, providing the V flag is set. + + //if (GetTR00Pin() == false) // active low + //{ + //} + //else + //{ + // // Now teleport + // currentTrack = 0; + //} + + //DEBUG_LOG("RESTORE\r\n"); + + commandType = 1; + break; + case SEEK: + // This command assumes that the Track Register contains the track number of the current position of the Read / Write head and the Data Register contains the desired track number. + + // Issue stepping pulses in the appropriate direction until the contents of the Track Register are equal to the contents of the Data Register. + + // An interrupt is generated at the completion of the command. + + // DEBUG_LOG("SEEK\r\n"); + + commandType = 1; + break; + + case STEP: + case STEP + 1: + // The WD177x issues one Stepping Pulse to the disk drive. + // The stepping motor direction is the same as in the previous step command. + // After a delay determined by the r1, r0 field. + // If the U flag is on, the Track Register is updated. + + // An interrupt is generated at the completion of the command. + + //DEBUG_LOG("STEP\r\n"); + + commandType = 1; + break; + case STEP_IN: + case STEP_IN + 1: + // The WD177x issues one Stepping Pulse In the direction towards track 80. + // if the U flag is on, the Track Register is incremented by one. + // After a delay determined by the r1, r0 field + + // An interrupt is generated at the completion of the command. + stepDirection = 1; + + //DEBUG_LOG("STEP IN\r\n"); + + commandType = 1; + break; + case STEP_OUT: + case STEP_OUT + 1: + // The WD177x issues one Stepping Pulse In the direction towards track 0. + // if the U flag is on, the Track Register is incremented by one. + // After a delay determined by the r1, r0 field + + // An interrupt is generated at the completion of the command. + stepDirection = -1; + stepcount++; + //DEBUG_LOG("STEP OUT %d\r\n", stepcount); + + commandType = 1; + break; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Type II Commands + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + case READ_SECTOR: + // The controller waits for a sector ID field that has the correct track number, sector number, and CRC. + // The controller then checks for the Data Address Mark, which consists of 43 copies of the second byte of the CRC. + // If the controller does not find a sector with correct ID field and address mark within 5 disk revolutions, the command ends. + // Once the 177x finds the desired sector, it loads the bytes of that sector into the data register. + // If there is a CRC error at the end of the data field, the 177x sets the CRC Error bit in the Status Register and ends the command regardless of the state of the "m" flag. + + if (value & COMMANDBIT_E) + settleCycleDelay = 15000 * Mhz; // The settling time is 15ms for the 1772 and 30ms for the 1770. + else + settleCycleDelay = 0; + + //DEBUG_LOG("READ_SECTOR\r\n"); + readAddressState = SEARCHING_FOR_NEXT_ID; + + commandType = 2; + break; + + case WRITE_SECTOR: + // The 177x waits for a sector ID field with the correct track number, sector number, and CRC.The 177x then counts off 22 bytes from the CRC field. + // If the CPU has not loaded a byte into the Data Register before the end of this 22 - byte delay, the 177x ends the command. + // Assuming that the CPU has heeded the 177x's data request, the controller writes 12 bytes of zeroes. + // The 177x then writes a normal or deleted Data Address Mark according to the a0 flag of the command. + // Next, the 177x writes the byte which the CPU placed in the Data Register, and continues to request and write data bytes until the end of the sector. + // After the 177x writes the last byte, it calculates and writes the 16 - bit CRC.The chip then writes one $ff byte. + // The 177x interrupts the CPU 24 cycles after it writes the second byte of the CRC. + statusRegister = INDEX_DATAREQUEST; + + if (value & COMMANDBIT_E) + settleCycleDelay = 15000 * Mhz; // The settling time is 15ms for the 1772 and 30ms for the 1770. + else + settleCycleDelay = 0; + + + //DEBUG_LOG("WRITE_SECTOR\r\n"); + readAddressState = SEARCHING_FOR_NEXT_ID; + + commandType = 2; + break; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Type III Commands + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + case READ_ADDRESS: + settleCycleDelay = 0; + + readAddressState = SEARCHING_FOR_NEXT_ID; + + racount++; + //DEBUG_LOG("READ_ADDRESS %d\r\n", racount); + + commandType = 3; + break; + + case READ_TRACK: + // This command dumps a raw track, including gaps, ID fields, and data, into the Data Register. + + DEBUG_LOG("READ_TRACK\r\n"); + + commandType = 3; + break; + + case WRITE_TRACK: + // This command is the means of formatting disks. + + //During the format command the character loaded into the data register of the WD1772 is written to the disk. + //However the characters $F5 and $F6 are used to write respectively the Sync Characters $A1 and $C2 with a missing clock transition and the character $F7 is used to generate two CRC bytes. + //This implies that it is not possible to create a sector with an ID ranging from 245 through 247 ($F5 - $F7). + //In fact the WD1772 documentation indicates that the sector number should be kept in the range 1 to 240. + + // An $F7 input byte results in the FDC writing two CRC bytes with no $F7 in them at all. + // $FC - Index Address Mark (clock pattern D7) + // $FE - ID Address Mark (clock pattern C7) + // $FB - Data Address Mark (clock pattern C7) + // $F8 - Deleted Data Address Mark (clock pattern C7) + DEBUG_LOG("WRITE_TRACK\r\n"); + + commandType = 3; + break; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Type IV Commands + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + case FORCE_INTERRUPT: + // This command is loaded into the Command Register at any time. + // If there is a current command under execution(Busy Status Bit set) the command is terminated and the Busy Status Bit reset (the rest of the status bits are unchanged) + + if (statusRegister & BUSY) + { + // If the Force Interrupt Command is received when there is not a current command under execution, the Busy Status Bit is reset and the rest + // of the status bits are updated or cleared. In this case, Status reflects the Type I commands. + } + + CommandComplete(); + + DEBUG_LOG("FORCE_INTERRUPT\r\n"); + + + // Don't understand this? Shouldn't it be opposite (D8 enables the IRQ and D0 clears it)? + // Reading the status or writing to the Command Register does not automatically clear the IRQ. + // The Hex D0 is the only command that enables the immediate IRQ (D8) to clear on a subsequent load Command Register or Read Status Register operation. + // Follow a Hex D8 with D0 command. + if (value & 8) + { + // I3 + AssertIRQ(); + } + else + { + // I assume D0 releases the IRQ + ReleaseIRQ(); + } + + commandType = 4; + break; + } + } + } + break; + case TRACK: + // During disk reading, writing, and verifying, the 177x compares the Track Register to the track number in the sector ID field. + // When the 177x is busy, it ignores CPU writes to this register. (!Check) + trackRegister = value; + break; + case SECTOR: + // When the 177x is busy, it ignores CPU writes to this register. + sectorRegister = value; + break; + case DATA: + dataRegister = value; + // A write to the Data Register also causes both DRQ's to reset. + statusRegister &= ~INDEX_DATAREQUEST; + break; + } +} + +bool WD177x::SpinUp() +{ + if ((statusRegister & MOTOR_ON) == 0) + { + statusRegister |= MOTOR_ON; + + //DEBUG_LOG("Motor on\r\n"); + // Need to wait 6 revoiutions before executing the command. + motorSpinCount = CYCLES_8Mhz_PER_ROTATION * 6; + //statusRegister &= ~SPINUP_DATAMARK; + // Just spin it for now. + statusRegister |= SPINUP_DATAMARK; + return true; + } + return false; +} + +void WD177x::SpinDown() +{ + //DEBUG_LOG("Motor off\r\n"); + statusRegister &= ~MOTOR_ON; + statusRegister &= ~SPINUP_DATAMARK; +} + +void WD177x::AssertExternalMotor(bool assert) +{ + externalMotorAsserted = assert; + + if (externalMotorAsserted) + SpinUp(); + else + SpinDown(); +} + +void WD177x::SetDataRegister(unsigned char byteRead) +{ + if (statusRegister & INDEX_DATAREQUEST) // If the CPU has not read the last value yet + statusRegister |= TRACKZERO_LOSTDATA; // then it missed the bus. + statusRegister |= INDEX_DATAREQUEST; + dataRegister = byteRead; + //DEBUG_LOG("%02x\r\n", byteRead); +} + +unsigned char WD177x::GetDataRegister() +{ + if (statusRegister & INDEX_DATAREQUEST) // If the CPU has not written the last value yet + statusRegister |= TRACKZERO_LOSTDATA; // then it missed the bus. + statusRegister |= INDEX_DATAREQUEST; + return dataRegister; +} + +void WD177x::Insert(DiskImage* diskImage) +{ + this->diskImage = diskImage; + writeProtectAsserted = diskImage->GetReadOnly(); +} diff --git a/src/wd177x.h b/src/wd177x.h new file mode 100644 index 0000000..4820a32 --- /dev/null +++ b/src/wd177x.h @@ -0,0 +1,265 @@ +// Pi1541 - A Commodore 1541 disk drive emulator +// Copyright(C) 2018 Stephen White +// +// This file is part of Pi1541. +// +// Pi1541 is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Pi1541 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Pi1541. If not, see . + +#ifndef WD177X_H +#define WD177X_H + +#include "IOPort.h" +#include "DiskImage.h" +#include "m6502.h" + +//32x 4e +// For 10 sectors +// 12x 00 // SYNC +// 3x a1 +// 1x fe // ID +// 1x TrackNo (0 indexed) +// 1x Head +// 1x sector (1 indexed) +// 1x 02 (sector size) +// 1x crc high byte +// 1x crc low byte +// 22x 4e (gap2) +// For 2 loops (logical sectors to physical sectors) +// if first loop +// 12x 00 // SYNC +// 3x a1 +// 1x fb // Data mark +// endif +// 256x data (indexed sequentailly through D81) +// end for +// 1x crc high byte +// 1x crc low byte +// 35x 4e (gap3) +// end for + +//PHYSICAL: +// Cylinders 0 thru 79 +// Sectors 1 thru 10 on Side 1 +// Sectors 1 thru 10 on Side 2 +// Sector Size 512 +//LOGICAL : +// Tracks 1 thru 80 +// Sectors 0 thru 39 (Using physical Sectors 1 ... 10 - Side 1 and 2) +// Sector Size 256 Bytes + + +// This emulator emulates at a command level and does not go into emulating actual micro controller instructions. + +#define CYCLES_8Mhz_PER_INDEX_HOLE 64 + +class WD177x +{ + + enum Registers + { + STATUS = 0, + COMMAND = 0, + TRACK, + SECTOR, + DATA + }; + + enum StatusBits + { + // This bit is 1 when the 177x is busy. This bit is 0 when the 177x is free for CPU commands. + BUSY = 0x01, // 0 + + // For Type I commands, this bit is high during the index pulse that occurs once per disk rotation low otherwise. + // For Type II and III commands, high signals the CPU to handle the data register in order to maintain a continuous flow of data. + // High when the data register is full during a read or when the data register is empty during a write. + INDEX_DATAREQUEST = 0x02, // 1 + + // For Type I commands, this bit is 0 if the mechanism is at track zero and 1 if the head is not. + // For Type II or III commands, this bit is 1 if the CPU did not respond to Data Request (INDEX_DATAREQUEST) in time for the 177x to maintain a continuous data flow. + TRACKZERO_LOSTDATA = 0x04, // 2 + + CRC_ERROR = 0x08, // 3 + + // This bit is set if the 177x cannot find the track, sector, or side. + SEEK_ERROR = 0x10, // 4 + + // For Type I commands, this bit is low during the 6 - revolution motor spin - up time. + // For Type II and Type III commands, low indicates a normal data mark, high indicates a deleted data mark. + SPINUP_DATAMARK = 0x20, // 5 + + WRITE_PROTECT = 0x40, // 6 + MOTOR_ON = 0x80 // 7 + }; + + // Command Bit 7 B6 B5 B4 B3 B2 B1 Bit 0 + // -------- ----- -- -- -- -- -- -- ----- + // Restore 0 0 0 0 h V r1 r0 + // Seek 0 0 0 1 h V r1 r0 + // Step 0 0 1 u h V r1 r0 + // Step in 0 1 0 u h V r1 r0 + // Step out 0 1 1 u h V r1 r0 + + enum Commands + { + RESTORE = 0, + SEEK = 1, + STEP = 2, + STEP_IN = 4, + STEP_OUT = 6, + + READ_SECTOR = 8, + WRITE_SECTOR = 0xa, + + READ_ADDRESS = 0xc, + + FORCE_INTERRUPT = 0xd, + + READ_TRACK = 0xe, + WRITE_TRACK = 0xf, + }; + + enum CommandBit + { + COMMANDBIT_A = 0x01, + COMMANDBIT_H = 0x08, + COMMANDBIT_U = 0x10, + COMMANDBIT_E = 0x04, + COMMANDBIT_M = 0x10 + }; + + enum CommandSubState + { + SEARCHING_FOR_NEXT_ID, + WAIT_FOR_TRACK, + WAIT_FOR_SIDE, + WAIT_FOR_SECTOR, + WAIT_FOR_SECTOR_LENGTH, + SEARCHING_FOR_NEXT_DATAID = WAIT_FOR_SECTOR_LENGTH, + WAIT_FOR_CRC1, + WAIT_FOR_CRC2, + READING_SECTOR, + WRITING_SECTOR + }; + +public: + + WD177x(); + + inline void ConnectIRQ(Interrupt* irq) { this->irq = irq; } + + void Insert(DiskImage* diskImage); + + void Reset(); + + void Execute(); + + unsigned char Read(unsigned int address); + unsigned char Peek(unsigned int address); + void Write(unsigned int address, unsigned char value); + + inline bool GetDIRECTIONPin() const { return stepDirection > 0; } + //bool GetSTEPPin() const { return STEPPulse; } + inline bool GetTR00Pin() const { return !(currentTrack == 0); } // active low + // IP is the index pulse + inline bool GetIPPin() const { return !(rotationCycle < CYCLES_8Mhz_PER_INDEX_HOLE); } // active low + //bool GetDRQPin() const { return ; } // This active high output indicates that the Data Register is full(on a Read) or empty(on a Write operation). + bool GetWPRTPin() const; // active low + void SetWPRTPin(bool value); // active low + + inline unsigned int GetCurrentTrack() const { return currentTrack; } + +// void SetSide(int side) { currentSide = (side ^ 1); } + void SetSide(int side) { currentSide = side; } + void AssertExternalMotor(bool assert); + inline bool IsExternalMotorAsserted() const { return externalMotorAsserted; } + +private: + bool SpinUp(); + void SpinDown(); + + void UpdateCommandType1(); + void UpdateCommandType2(); + void UpdateCommandType3(); + void UpdateCommandType4(); + void CommandComplete(); + + bool CheckForSeekError() + { + // SEEK_ERROR after 5 disk revolutions without finding track/sector + return rotationCountForSeekError > 5; + } + + //void UpdateReadWriteByte(); + bool ReadByte(unsigned char& byte, bool& syncByte); + + void AssertIRQ(); + void ReleaseIRQ(); + + void SetDataRegister(unsigned char byteRead); + //inline void SetDataRegister(unsigned char byteRead) + //{ + // if (statusRegister & INDEX_DATAREQUEST) // If the CPU has not read the last value yet + // statusRegister |= TRACKZERO_LOSTDATA; // then it missed the bus. + // statusRegister |= INDEX_DATAREQUEST; + // dataRegister = byteRead; + //} + unsigned char GetDataRegister(); + + unsigned char trackRegister; + unsigned char sectorRegister; + unsigned char statusRegister; + unsigned char dataRegister; + unsigned char currentByteRead; + unsigned char sectorFromHeader; + + unsigned char commandRegister; + unsigned char commandRegisterPrevious; + unsigned char commandType; + + unsigned int motorSpinCount; + + int currentTrack; + int currentSide; + bool externalMotorAsserted; + bool writeProtectAsserted; + + int stepDirection; + + unsigned char command; + unsigned char commandValue; + unsigned int commandStage; + + unsigned int headDataOffset; + bool lastByteWasASync; + + DiskImage* diskImage; + + unsigned int rotationCountForSeekError; + unsigned int rotationCycle; + unsigned int byteRotationCycle; + unsigned int inactiveRotationCount; + unsigned int settleCycleDelay; + + unsigned int readAddressState; + unsigned int sectorByteIndex; + + Interrupt* irq; + + int delayTimer; + + unsigned short crc; + unsigned char dataAddressMark; +}; + +#endif \ No newline at end of file