C64: Allow writing to disk.

This commit is contained in:
SaxxonPike 2019-07-04 00:11:03 -05:00
parent 0cdb28fc8f
commit 4e1892d094
5 changed files with 304 additions and 204 deletions

View File

@ -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)
{

View File

@ -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<byte[]>();
var trackLengths = new List<int>();
var trackNumbers = new List<int>();
var trackDensities = new List<int>();
int trackCount;
using (var mem = new MemoryStream(source))
{
var reader = new BinaryReader(mem);
var trackDatas = new List<byte[]>();
var trackLengths = new List<int>();
var trackNumbers = new List<int>();
var trackDensities = new List<int>();
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);
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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)

View File

@ -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()