454 lines
11 KiB
C++
Executable File
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;
|
|
}
|