Files
VirtualC64-Core/C64/VC1541.cpp
T
2015-11-18 00:34:41 -06:00

454 lines
11 KiB
C++
Executable File

/*
* Written 2006 - 2015 by Dirk W. Hoffmann
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "C64.h"
VC1541::VC1541()
{
name = "1541";
debug(2, "Creating virtual VC1541 at address %p\n", this);
// Create sub components
mem = new VC1541Memory();
cpu = new CPU();
cpu->setName("1541CPU");
cpu->chipModel = CPU::MOS6502;
// Register sub components
VirtualComponent *subcomponents[] = { mem, cpu, &via1, &via2, &disk, NULL };
registerSubComponents(subcomponents, sizeof(subcomponents));
// Register snapshot items
SnapshotItem items[] = {
// Configuration items
{ &bitAccuracy, sizeof(bitAccuracy), KEEP_ON_RESET },
{ &sendSoundMessages, sizeof(sendSoundMessages), KEEP_ON_RESET },
// Internal state
{ &bitReadyTimer, sizeof(bitReadyTimer), CLEAR_ON_RESET },
{ &byteReadyCounter, sizeof(byteReadyCounter), CLEAR_ON_RESET },
{ &rotating, sizeof(rotating), CLEAR_ON_RESET },
{ &redLED, sizeof(redLED), CLEAR_ON_RESET },
{ &diskPartiallyInserted, sizeof(diskPartiallyInserted), CLEAR_ON_RESET },
{ &halftrack, sizeof(halftrack), CLEAR_ON_RESET },
{ &bitoffset, sizeof(bitoffset), CLEAR_ON_RESET },
{ &zone, sizeof(zone), CLEAR_ON_RESET },
{ &read_shiftreg, sizeof(read_shiftreg), CLEAR_ON_RESET },
{ &write_shiftreg, sizeof(write_shiftreg), CLEAR_ON_RESET },
{ &sync, sizeof(sync), CLEAR_ON_RESET },
// Disk properties (will survive reset)
{ &diskInserted, sizeof(diskInserted), KEEP_ON_RESET },
{ NULL, 0, 0 }};
registerSnapshotItems(items, sizeof(items));
sendSoundMessages = true;
resetDisk();
}
VC1541::~VC1541()
{
debug(2, "Releasing VC1541...\n");
delete cpu;
delete mem;
}
void
VC1541::reset()
{
VirtualComponent::reset();
// Establish bindings
iec = c64->iec;
cpu->mem = mem;
cpu->setPC(0xEAA0);
halftrack = 41;
}
void
VC1541::resetDisk()
{
debug (2, "Resetting disk in VC1541...\n");
// Disk properties
disk.clearDisk();
diskInserted = false;
diskPartiallyInserted = false;
}
void
VC1541::ping()
{
debug(2, "Pinging VC1541...\n");
c64->putMessage(MSG_VC1541_LED, redLED ? 1 : 0);
c64->putMessage(MSG_VC1541_MOTOR, rotating ? 1 : 0);
c64->putMessage(MSG_VC1541_DISK, diskInserted ? 1 : 0);
cpu->ping();
mem->ping();
via1.ping();
via2.ping();
}
#if 0
uint32_t
VC1541::stateSize()
{
uint32_t result = VirtualComponent::stateSize();
result += disk.stateSize();
result += cpu->stateSize();
result += via1.stateSize();
result += via2.stateSize();
result += mem->stateSize();
return result;
}
void
VC1541::loadFromBuffer(uint8_t **buffer)
{
uint8_t *old = *buffer;
VirtualComponent::loadFromBuffer(buffer);
disk.loadFromBuffer(buffer);
cpu->loadFromBuffer(buffer);
via1.loadFromBuffer(buffer);
via2.loadFromBuffer(buffer);
mem->loadFromBuffer(buffer);
assert(*buffer - old == stateSize());
}
void
VC1541::saveToBuffer(uint8_t **buffer)
{
uint8_t *old = *buffer;
VirtualComponent::saveToBuffer(buffer);
disk.saveToBuffer(buffer);
cpu->saveToBuffer(buffer);
via1.saveToBuffer(buffer);
via2.saveToBuffer(buffer);
mem->saveToBuffer(buffer);
assert(*buffer - old == stateSize());
}
#endif
void
VC1541::dumpState()
{
msg("VC1541\n");
msg("------\n\n");
msg(" Bit ready timer : %d\n", bitReadyTimer);
msg(" Head position : Track %d, Bit offset %d\n", halftrack, bitoffset);
msg(" SYNC : %d\n", sync);
msg(" Read mode : %s\n", readMode() ? "YES" : "NO");
msg("\n");
disk.dumpState();
}
void
VC1541::executeBitReady()
{
read_shiftreg <<= 1;
if (readMode()) {
// Read mode
read_shiftreg |= readBitFromHead();
// Set SYNC signal
if ((read_shiftreg & 0x3FF) == 0x3FF) {
sync = true;
} else {
if (sync)
byteReadyCounter = 0; // Cleared on falling edge of SYNC
sync = false;
}
} else {
// Write mode
writeBitToHead(write_shiftreg & 0x80);
disk.setModified(true);
sync = false;
}
write_shiftreg <<= 1;
rotateDisk();
// Perform action if byte is complete
if (byteReadyCounter++ == 7) {
executeByteReady();
byteReadyCounter = 0;
}
bitReadyTimer += cyclesPerBit[zone];
}
void
VC1541::executeByteReady()
{
// assert(bitoffset % 8 == 0);
if (readMode() && !sync) {
byteReady(read_shiftreg);
}
if (writeMode()) {
write_shiftreg = via2.ora;
byteReady();
}
}
inline void
VC1541::byteReady(uint8_t byte)
{
// On the VC1541 logic board, the byte ready signal is computed by a NAND gate with three inputs.
// Two of them are clock lines ensuring that a signal is generated every eigths bit.
// The third signal is hard-wired to pin CA2 of VIA2. By pulling CA2 low, the CPU can silence the
// the byte ready line. E.g., this is done when moving the drive head to a different track
if (via2.CA2()) {
via2.ira = byte;
byteReady();
}
}
inline void
VC1541::byteReady()
{
if (via2.overflowEnabled()) cpu->setV(1);
}
void
VC1541::simulateAtnInterrupt()
{
if (via1.atnInterruptsEnabled()) {
via1.indicateAtnInterrupt();
cpu->setIRQLineATN();
// debug("CPU is interrupted by ATN line.\n");
} else {
// debug("Sorry, want to interrupt, but CPU does not accept ATN line interrupts\n");
}
}
void
VC1541::setZone(uint8_t z)
{
assert (z <= 3);
if (z != zone) {
debug(3, "Switching from disk zone %d to disk zone %d\n", zone, z);
zone = z;
}
}
void
VC1541::setRedLED(bool b)
{
if (!redLED && b) {
redLED = true;
c64->putMessage(MSG_VC1541_LED, 1);
} else if (redLED && !b) {
redLED = false;
c64->putMessage(MSG_VC1541_LED, 0);
}
}
void
VC1541::setRotating(bool b)
{
if (!rotating && b) {
rotating = true;
c64->putMessage(MSG_VC1541_MOTOR, 1);
} else if (rotating && !b) {
rotating = false;
c64->putMessage(MSG_VC1541_MOTOR, 0);
}
}
void
VC1541::moveHeadUp()
{
if (halftrack < 84) {
float position = (float)bitoffset / (float)disk.length.halftrack[halftrack];
halftrack++;
bitoffset = position * disk.length.halftrack[halftrack];
// Make sure new bitoffset starts at the beginning of a new byte to keep fast loader happy
alignHead();
debug(3, "Moving head up to halftrack %d (track %2.1f) bit accurate emulation: %s\n",
halftrack, (halftrack + 1) / 2.0, bitAccuracy ? "YES" : "NO");
}
assert(disk.isValidDiskPositon(halftrack, bitoffset));
c64->putMessage(MSG_VC1541_HEAD, 1);
if (halftrack % 2 && sendSoundMessages)
c64->putMessage(MSG_VC1541_HEAD_SOUND, 1); // play sound for full tracks, only
}
void
VC1541::moveHeadDown()
{
if (halftrack > 1) {
float position = (float)bitoffset / (float)disk.length.halftrack[halftrack];
halftrack--;
bitoffset = position * disk.length.halftrack[halftrack];
// Make sure new bitoffset starts at the beginning of a new byte to keep fast loader happy
alignHead();
debug(3, "Moving head down to halftrack %d (track %2.1f) bit accurate emulation: %s\n",
halftrack, (halftrack + 1) / 2.0, bitAccuracy ? "YES" : "NO");
}
assert(disk.isValidDiskPositon(halftrack, bitoffset));
c64->putMessage(MSG_VC1541_HEAD, 0);
if (halftrack % 2 && sendSoundMessages)
c64->putMessage(MSG_VC1541_HEAD_SOUND, 0); // play sound for full tracks, only
}
void
VC1541::setBitAccuracy(bool b)
{
bitAccuracy = b;
if (!b) { // If bit accuracy is disabled, ...
// we align the drive head to the beginning of a byte
alignHead();
// and write-protect the disk.
disk.setWriteProtection(true);
}
}
void
VC1541::insertDisk(Archive *a)
{
assert(a != NULL);
D64Archive *d64 = (D64Archive *)a;
G64Archive *g64 = (G64Archive *)a;
switch (a->getType()) {
case D64_CONTAINER:
ejectDisk();
disk.encodeArchive(d64);
break;
case G64_CONTAINER:
ejectDisk();
disk.encodeArchive(g64);
break;
default:
warn("Only D64 or G64 archives can be mounted as virtual disk.");
return;
}
diskInserted = true;
c64->putMessage(MSG_VC1541_DISK, 1);
if (sendSoundMessages)
c64->putMessage(MSG_VC1541_DISK_SOUND, 1);
// If bit accuracy is disabled, we write-protect the disk
disk.setWriteProtection(true);
}
void
VC1541::ejectDisk()
{
if (!hasDisk())
return;
// Open lid (this blocks the light barrier)
setDiskPartiallyInserted(true);
// Let the drive notice the blocked light barrier in its interrupt routine ...
sleepMicrosec((uint64_t)200000);
// Erase disk data and reset write protection flag
resetDisk();
// Remove disk (this unblocks the light barrier)
setDiskPartiallyInserted(false);
// Notify listener
c64->putMessage(MSG_VC1541_DISK, 0);
if (sendSoundMessages)
c64->putMessage(MSG_VC1541_DISK_SOUND, 0);
}
bool
VC1541::exportToD64(const char *filename)
{
D64Archive *archive;
assert(filename != NULL);
// Create archive
if ((archive = D64Archive::archiveFromDrive(this)) == NULL)
return false;
// Write archive to disk
archive->writeToFile(filename);
delete archive;
return true;
}
void
VC1541::fastLoaderRead()
{
uint8_t byteUnderHead = readByteFromHead();
byteReady(byteUnderHead);
if (byteUnderHead == 0xFF) {
fastLoaderSkipSyncMark(); // If we're inside a SYNC mark, proceed to next data byte
} else {
rotateDiskByOneByte(); // If we're outside a SYNC mark, the next data byte is just one byte ahead
}
}
bool
VC1541::getFastLoaderSync()
{
uint8_t byteUnderHead = readByteFromHead();
rotateDiskByOneByte();
return byteUnderHead == 0xFF;
}