From 4e1892d0947a4b30b7d5bb44849e6c80fa70e1b1 Mon Sep 17 00:00:00 2001 From: SaxxonPike Date: Thu, 4 Jul 2019 00:11:03 -0500 Subject: [PATCH] C64: Allow writing to disk. --- .../Computers/Commodore64/MOS/Via.cs | 80 +++--- .../Computers/Commodore64/Media/D64.cs | 162 ++++++----- .../Serial/Drive1541.FluxTransitions.cs | 259 ++++++++++-------- .../Commodore64/Serial/Drive1541.Registers.cs | 2 +- .../Computers/Commodore64/Serial/Drive1541.cs | 5 + 5 files changed, 304 insertions(+), 204 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Via.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Via.cs index a2729101e2..147449bf9c 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Via.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Via.cs @@ -129,15 +129,15 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS _acrSrControl = 0; _acrT1Control = 0; _acrT2Control = 0; - _ca1L = false; - _cb1L = false; - Ca1 = false; - Ca2 = false; - Cb1 = false; - Cb2 = false; + _ca1L = true; + _cb1L = true; + Ca1 = true; + Ca2 = true; + Cb1 = true; + Cb2 = true; - _pb6L = false; - _pb6 = false; + _pb6L = true; + _pb6 = true; _resetCa2NextClock = false; _resetCb2NextClock = false; _handshakeCa2NextClock = false; @@ -222,9 +222,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS switch (_acrT1Control) { case ACR_T1_CONTROL_CONTINUOUS_INTERRUPTS: - _t1C = _t1L; - _t1CLoaded = true; - break; case ACR_T1_CONTROL_CONTINUOUS_INTERRUPTS_AND_OUTPUT_ON_PB7: _t1C = _t1L; _t1CLoaded = true; @@ -261,11 +258,11 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS if (!_pb6 && _pb6L) { _t2C--; - if (_t2C < 0) + if (_t2C == 0) { _ifr |= 0x20; - _t2C = 0xFFFF; } + _t2C &= 0xFFFF; } break; } @@ -331,26 +328,45 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS break; } - // interrupt generation - if ((_pcrCb1IntControl == PCR_INT_CONTROL_POSITIVE_EDGE && Cb1 && !_cb1L) || - (_pcrCb1IntControl == PCR_INT_CONTROL_NEGATIVE_EDGE && !Cb1 && _cb1L)) - { - _ifr |= 0x10; - if (_acrPbLatchEnable) - { - _pbLatch = _port.ReadExternalPrb(); - } - } + // interrupt generation - if ((_pcrCa1IntControl == PCR_INT_CONTROL_POSITIVE_EDGE && Ca1 && !_ca1L) || - (_pcrCa1IntControl == PCR_INT_CONTROL_NEGATIVE_EDGE && !Ca1 && _ca1L)) - { - _ifr |= 0x02; - if (_acrPaLatchEnable) - { - _paLatch = _port.ReadExternalPra(); - } - } + /* + As long as the CA1 interrupt flag is set, the data on the peripheral pins can change + without affecting the data in the latches. This input latching can be used with any of the CA2 + input or output modes. + It is important to note that on the PA port, the processor always reads the data on the + peripheral pins (as reflected in the latches). For output pins, the processor still reads the + latches. This may or may not reflect the data currently in the ORA. Proper system operation + requires careful planning on the part of the system designer if input latching is combined + with output pins on the peripheral ports. + */ + + if ((_pcrCa1IntControl == PCR_INT_CONTROL_POSITIVE_EDGE && Ca1 && !_ca1L) || + (_pcrCa1IntControl == PCR_INT_CONTROL_NEGATIVE_EDGE && !Ca1 && _ca1L)) + { + if (_acrPaLatchEnable && (_ifr & 0x02) == 0) + { + _paLatch = _port.ReadExternalPra(); + } + _ifr |= 0x02; + } + + /* + Input latching on the PB port is controlled in the same manner as that described for the PA port. + However, with the peripheral B port the input latch will store either the voltage on the pin or the contents + of the Output Register (ORB) depending on whether the pin is programmed to act as an input or an + output. As with the PA port, the processor always reads the input latches. + */ + + if ((_pcrCb1IntControl == PCR_INT_CONTROL_POSITIVE_EDGE && Cb1 && !_cb1L) || + (_pcrCb1IntControl == PCR_INT_CONTROL_NEGATIVE_EDGE && !Cb1 && _cb1L)) + { + if (_acrPbLatchEnable && (_ifr & 0x10) == 0) + { + _pbLatch = _port.ReadPrb(_prb, _ddrb); + } + _ifr |= 0x10; + } switch (_acrSrControl) { diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/Media/D64.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/Media/D64.cs index fd58006c5d..082fc32b0a 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/Media/D64.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/Media/D64.cs @@ -9,6 +9,22 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media { const int D64_DISK_ID_OFFSET = 0x165A2; // track 18, sector 0, 0xA2 + private enum ErrorType + { + NoError = 0x01, + HeaderNotFound = 0x02, + NoSyncSequence = 0x03, + DataNotFound = 0x04, + DataChecksumError = 0x05, + WriteVerifyFormatError = 0x06, + WriteVerifyError = 0x07, + WriteProtectOn = 0x08, + HeaderChecksumError = 0x09, + WriteError = 0x0A, + IdMismatch = 0x0B, + DriveNotReady = 0x0F + } + private static readonly int[] DensityTable = { 3, 3, 3, 3, 3, @@ -84,25 +100,34 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media return result; } - private static byte[] ConvertSectorToGcr(byte[] source, byte sectorNo, byte trackNo, byte formatA, byte formatB, int gapLength, out int bitsWritten) + private static byte[] ConvertSectorToGcr(byte[] source, byte sectorNo, byte trackNo, byte formatA, byte formatB, int gapLength, ErrorType errorType, out int bitsWritten) { using (var mem = new MemoryStream()) { var writer = new BinaryWriter(mem); - var headerChecksum = (byte)(sectorNo ^ trackNo ^ formatA ^ formatB); + + if (errorType == ErrorType.IdMismatch) + { + formatA ^= 0xFF; + formatB ^= 0xFF; + } + + var headerChecksum = (byte)(sectorNo ^ trackNo ^ formatA ^ formatB ^ (errorType == ErrorType.HeaderChecksumError ? 0xFF : 0x00)); // assemble written data for GCR encoding var writtenData = new byte[260]; + var syncBytes40 = Enumerable.Repeat((byte) (errorType == ErrorType.NoSyncSequence ? 0x00 : 0xFF), 5).ToArray(); + Array.Copy(source, 0, writtenData, 1, 256); - writtenData[0] = 0x07; - writtenData[0x101] = Checksum(source); + writtenData[0] = (byte)(errorType == ErrorType.HeaderNotFound ? 0x00 : 0x07); + writtenData[0x101] = (byte)(Checksum(source) ^ (errorType == ErrorType.DataChecksumError ? 0xFF : 0x00)); writtenData[0x102] = 0x00; writtenData[0x103] = 0x00; - writer.Write(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }); // sync - writer.Write(EncodeGcr(new byte[] { 0x08, headerChecksum, sectorNo, trackNo, formatA, formatB, 0x0F, 0x0F })); // header + writer.Write(syncBytes40); // sync + writer.Write(EncodeGcr(new byte[] { (byte)(errorType == ErrorType.DataNotFound ? 0x00 : 0x08), headerChecksum, sectorNo, trackNo, formatA, formatB, 0x0F, 0x0F })); // header writer.Write(new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }); // gap - writer.Write(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }); // sync + writer.Write(syncBytes40); // sync writer.Write(EncodeGcr(writtenData)); // data writer.Write(Enumerable.Repeat((byte)0x55, gapLength).ToArray()); // gap @@ -156,67 +181,76 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media public static Disk Read(byte[] source) { - var formatB = source[D64_DISK_ID_OFFSET + 0x00]; - var formatA = source[D64_DISK_ID_OFFSET + 0x01]; + var formatB = source[D64_DISK_ID_OFFSET + 0x00]; + var formatA = source[D64_DISK_ID_OFFSET + 0x01]; - using (var mem = new MemoryStream(source)) - { - var reader = new BinaryReader(mem); - var trackDatas = new List(); - var trackLengths = new List(); - var trackNumbers = new List(); - var trackDensities = new List(); - int trackCount; + using (var mem = new MemoryStream(source)) + { + var reader = new BinaryReader(mem); + var trackDatas = new List(); + var trackLengths = new List(); + var trackNumbers = new List(); + var trackDensities = new List(); + var errorType = ErrorType.NoError; + int trackCount; + int errorOffset = -1; - switch (source.Length) - { - case 174848: // 35 tracks no errors - trackCount = 35; - break; - case 175531: // 35 tracks with errors - trackCount = 35; - break; - case 196608: // 40 tracks no errors - trackCount = 40; - break; - case 197376: // 40 tracks with errors - trackCount = 40; - break; - default: - throw new Exception("Not able to identify capacity of the D64 file."); - } + switch (source.Length) + { + case 174848: // 35 tracks no errors + trackCount = 35; + break; + case 175531: // 35 tracks with errors + trackCount = 35; + errorOffset = 174848; + break; + case 196608: // 40 tracks no errors + trackCount = 40; + break; + case 197376: // 40 tracks with errors + trackCount = 40; + errorOffset = 196608; + break; + default: + throw new Exception("Not able to identify capacity of the D64 file."); + } - for (var i = 0; i < trackCount; i++) - { - var sectors = SectorsPerTrack[i]; - var trackLengthBits = 0; - using (var trackMem = new MemoryStream()) - { - for (var j = 0; j < sectors; j++) - { - int bitsWritten; - var sectorData = reader.ReadBytes(256); - var diskData = ConvertSectorToGcr(sectorData, (byte)j, (byte)(i + 1), formatA, formatB, StandardSectorGapLength[DensityTable[i]], out bitsWritten); - trackMem.Write(diskData, 0, diskData.Length); - trackLengthBits += bitsWritten; - } - var density = DensityTable[i]; + for (var i = 0; i < trackCount; i++) + { + if (errorOffset >= 0) + { + errorType = (ErrorType) source[errorOffset]; + errorOffset++; + } + var sectors = SectorsPerTrack[i]; + var trackLengthBits = 0; + using (var trackMem = new MemoryStream()) + { + for (var j = 0; j < sectors; j++) + { + int bitsWritten; + var sectorData = reader.ReadBytes(256); + var diskData = ConvertSectorToGcr(sectorData, (byte)j, (byte)(i + 1), formatA, formatB, StandardSectorGapLength[DensityTable[i]], errorType, out bitsWritten); + trackMem.Write(diskData, 0, diskData.Length); + trackLengthBits += bitsWritten; + } + var density = DensityTable[i]; - // we pad the tracks with extra gap bytes to meet MNIB standards - while (trackMem.Length < StandardTrackLengthBytes[density]) - { - trackMem.WriteByte(0x55); - } + // we pad the tracks with extra gap bytes to meet MNIB standards + while (trackMem.Length < StandardTrackLengthBytes[density]) + { + trackMem.WriteByte(0x55); + } - trackDatas.Add(trackMem.ToArray()); - trackLengths.Add(trackLengthBits); - trackNumbers.Add(i * 2); - trackDensities.Add(DensityTable[i]); - } - } - - return new Disk(trackDatas, trackNumbers, trackDensities, 84); - } - } + trackDatas.Add(trackMem.ToArray()); + trackLengths.Add(trackLengthBits); + trackNumbers.Add(i * 2); + trackDensities.Add(DensityTable[i]); + } + } + + return new Disk(trackDatas, trackNumbers, trackDensities, 84); + } + } } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs index 1252524211..2cdd555caf 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs @@ -19,6 +19,11 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial private int _rngCurrent; private int _clocks; private int _cpuClocks; + private int _diskWriteBitsRemaining; + private bool _diskWriteEnabled; + private int _diskWriteLatch; + private int _diskOutputBits; + private bool _diskWriteProtected; // Lehmer RNG private void AdvanceRng() @@ -31,127 +36,167 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial _rngCurrent = (int)(_rngCurrent * LEHMER_RNG_PRIME % int.MaxValue); } - private void ExecuteFlux() - { - // This actually executes the main 16mhz clock - while (_clocks > 0) - { - _clocks--; + private void ExecuteFlux() + { + // This actually executes the main 16mhz clock + while (_clocks > 0) + { + _clocks--; - // rotate disk - if (_motorEnabled) - { - if (_disk == null) - { - _diskBitsLeft = 1; - _diskBits = 0; - } - else - { - if (_diskBitsLeft <= 0) - { - _diskByteOffset++; - if (_diskByteOffset == Disk.FluxEntriesPerTrack) - { - _diskByteOffset = 0; - } + // rotate disk + if (_motorEnabled) + { + if (_disk == null) + { + _diskBitsLeft = 1; + _diskBits = 0; + } + else + { + if (_diskBitsLeft <= 0) + { + if (_diskWriteEnabled) + _trackImageData[_diskByteOffset] = _diskOutputBits; - _diskBits = _trackImageData[_diskByteOffset]; - _diskBitsLeft = Disk.FluxBitsPerEntry; - } - } + _diskByteOffset++; - if ((_diskBits & 1) != 0) - { - _countsBeforeRandomTransition = 0; - _diskFluxReversalDetected = true; - } + if (_diskByteOffset == Disk.FluxEntriesPerTrack) + _diskByteOffset = 0; - _diskBits >>= 1; - _diskBitsLeft--; - } + if (!_diskWriteEnabled) + _diskBits = _trackImageData[_diskByteOffset]; - // random flux transition readings for unformatted data - if (_countsBeforeRandomTransition > 0) - { - _countsBeforeRandomTransition--; - if (_countsBeforeRandomTransition == 0) - { - _diskFluxReversalDetected = true; - AdvanceRng(); + _diskOutputBits = 0; + _diskBitsLeft = Disk.FluxBitsPerEntry; + } + } + _diskOutputBits >>= 1; - // This constant is what VICE uses. TODO: Determine accuracy. - _countsBeforeRandomTransition = (_rngCurrent % 367) + 33; - } - } + if (_diskWriteEnabled) + _countsBeforeRandomTransition = 0; - // flux transition circuitry - if (_diskFluxReversalDetected) - { - _diskDensityCounter = _diskDensity; - _diskSupplementaryCounter = 0; - _diskFluxReversalDetected = false; - if (_countsBeforeRandomTransition == 0) - { - AdvanceRng(); + if ((_diskBits & 1) != 0) + { + _countsBeforeRandomTransition = 0; + _diskFluxReversalDetected = true; + _diskOutputBits |= int.MinValue; // set bit 31 + } + else + { + _diskOutputBits &= int.MaxValue; // clear bit 31 + } - // This constant is what VICE uses. TODO: Determine accuracy. - _countsBeforeRandomTransition = (_rngCurrent & 0x1F) + 289; - } - } + _diskBits >>= 1; + _diskBitsLeft--; + } - // counter circuitry - if (_diskDensityCounter >= 16) - { - _diskDensityCounter = _diskDensity; - _diskSupplementaryCounter++; - if ((_diskSupplementaryCounter & 0x3) == 0x2) - { - _bitsRemainingInLatchedByte--; - _byteReady = false; - _bitHistory = (_bitHistory << 1) | ((_diskSupplementaryCounter & 0xC) == 0x0 ? 1 : 0); - _sync = false; - if (Via1.Cb2 && (_bitHistory & 0x3FF) == 0x3FF) - { - _sync = true; - _bitsRemainingInLatchedByte = 8; - _byteReady = false; - } + // random flux transition readings for unformatted data + if (_countsBeforeRandomTransition > 0) + { + _countsBeforeRandomTransition--; + if (_countsBeforeRandomTransition == 0) + { + _diskFluxReversalDetected = true; + AdvanceRng(); + // This constant is what VICE uses. TODO: Determine accuracy. + _countsBeforeRandomTransition = (_rngCurrent % 367) + 33; + } + } - if (_bitsRemainingInLatchedByte <= 0) - { - _bitsRemainingInLatchedByte = 8; + // flux transition circuitry + if (_diskFluxReversalDetected) + { + if (!_diskWriteEnabled) + { + _diskDensityCounter = _diskDensity; + _diskSupplementaryCounter = 0; + } + _diskFluxReversalDetected = false; + if (_countsBeforeRandomTransition == 0) + { + AdvanceRng(); + // This constant is what VICE uses. TODO: Determine accuracy. + _countsBeforeRandomTransition = (_rngCurrent & 0x1F) + 289; + } + } - // SOE (sync output enabled) - _byteReady = Via1.Ca2; - } + // counter circuitry + if (_diskDensityCounter >= 16) + { + _diskDensityCounter = _diskDensity; + _diskSupplementaryCounter++; - // negative transition activates SO pin on CPU - _previousCa1 = Via1.Ca1; - Via1.Ca1 = !_byteReady; - if (_previousCa1 && !Via1.Ca1) - { - // cycle 6 is roughly 400ns - _overflowFlagDelaySr |= _diskCycle > 6 ? 4 : 2; - } - } - } + if ((_diskSupplementaryCounter & 0x3) == 0x2) + { + if (!_diskWriteEnabled) + _diskWriteBitsRemaining = 0; + _diskWriteEnabled = !Via1.Cb2; - if (_diskSupplementaryCounter >= 16) - { - _diskSupplementaryCounter = 0; - } + _diskWriteBitsRemaining--; + if (_diskWriteEnabled) + { + _countsBeforeRandomTransition = 0; + _byteReady = false; + if (_diskWriteBitsRemaining <= 0) + { + _diskWriteLatch = Via1.EffectivePrA; + _diskWriteBitsRemaining = 8; + _byteReady = Via1.Ca2; + } + if ((_diskWriteLatch & 0x80) != 0) + { + _diskOutputBits |= int.MinValue; // set bit 31 + } + _diskWriteLatch <<= 1; + } + else + { + _bitsRemainingInLatchedByte--; + _byteReady = false; + _bitHistory = (_bitHistory << 1) | ((_diskSupplementaryCounter & 0xC) == 0x0 ? 1 : 0); + _sync = false; + if (!_diskWriteEnabled && (_bitHistory & 0x3FF) == 0x3FF) + { + _sync = true; + _bitsRemainingInLatchedByte = 8; + _byteReady = false; + } - _cpuClocks--; - if (_cpuClocks <= 0) - { - ExecuteSystem(); - _cpuClocks = 16; - } + if (_bitsRemainingInLatchedByte <= 0) + { + _bitsRemainingInLatchedByte = 8; - _diskDensityCounter++; - _diskCycle = (_diskCycle + 1) & 0xF; - } - } + // SOE (SO/Byte Ready enabled) + _byteReady = Via1.Ca2; + } + } + } + + // negative transition activates SO pin on CPU + _previousCa1 = Via1.Ca1; + Via1.Ca1 = !_byteReady; + if (_previousCa1 && !Via1.Ca1) + { + // cycle 6 is roughly 400ns + _overflowFlagDelaySr |= _diskCycle > 6 ? 4 : 2; + } + } + + if (_diskSupplementaryCounter >= 16) + { + _diskSupplementaryCounter = 0; + } + + _cpuClocks--; + if (_cpuClocks <= 0) + { + ExecuteSystem(); + _cpuClocks = 16; + } + + _diskDensityCounter++; + _diskCycle = (_diskCycle + 1) & 0xF; + } + } } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.Registers.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.Registers.cs index 84e54cef51..02257c2253 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.Registers.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.Registers.cs @@ -46,7 +46,7 @@ private int ReadVia1PrB() { - return (_motorStep & 0x03) | (_motorEnabled ? 0x04 : 0x00) | (_sync ? 0x00 : 0x80); + return (_motorStep & 0x03) | (_motorEnabled ? 0x04 : 0x00) | (_sync ? 0x00 : 0x80) | (_diskWriteProtected ? 0x00 : 0x10); } public int Peek(int addr) diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs index d6556af21e..afe069f236 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs @@ -117,6 +117,11 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial ser.Sync("Clocks", ref _clocks); ser.Sync("CpuClocks", ref _cpuClocks); ser.Sync("OverflowFlagDelayShiftRegister", ref _overflowFlagDelaySr); + ser.Sync("DiskWriteBitsRemaining", ref _diskWriteBitsRemaining); + ser.Sync("DiskWriteEnabled", ref _diskWriteEnabled); + ser.Sync("DiskWriteLatch", ref _diskWriteLatch); + ser.Sync("DiskOutputBits", ref _diskOutputBits); + ser.Sync("DiskWriteProtected", ref _diskWriteProtected); } public override void ExecutePhase()