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

704 lines
23 KiB
C++
Executable File

/*
* (C) 2006 Dirk W. Hoffmann. All rights reserved.
*
* 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"
// -----------------------------------------------------------------------------------------------
// VIA 6522 (Commons)
// -----------------------------------------------------------------------------------------------
VIA6522::VIA6522()
{
name = "VIA";
// Register snapshot items
SnapshotItem items[] = {
{ &ddra, sizeof(ddra), CLEAR_ON_RESET },
{ &ddrb, sizeof(ddrb), CLEAR_ON_RESET },
{ &ora, sizeof(ora), CLEAR_ON_RESET },
{ &orb, sizeof(orb), CLEAR_ON_RESET },
{ &ira, sizeof(ira), CLEAR_ON_RESET },
{ &irb, sizeof(irb), CLEAR_ON_RESET },
{ &t1, sizeof(t1), CLEAR_ON_RESET },
{ &t2, sizeof(t2), CLEAR_ON_RESET },
{ &t1_latch_lo, sizeof(t1_latch_lo), CLEAR_ON_RESET },
{ &t1_latch_hi, sizeof(t1_latch_hi), CLEAR_ON_RESET },
{ &t2_latch_lo, sizeof(t2_latch_lo), CLEAR_ON_RESET },
{ &t1_underflow, sizeof(t1_underflow), CLEAR_ON_RESET },
{ &t2_underflow, sizeof(t2_underflow), CLEAR_ON_RESET },
{ io, sizeof(io), CLEAR_ON_RESET },
{ NULL, 0, 0 }};
registerSnapshotItems(items, sizeof(items));
}
VIA6522::~VIA6522()
{
}
void VIA6522::reset()
{
VirtualComponent::reset();
// Establish bindings
floppy = c64->floppy;
}
void
VIA6522::dumpState()
{
msg("VIA:\n");
msg("----\n\n");
msg(" Input register (IRA) : %02X\n", ira);
msg(" Input register (IRB) : %02X\n", irb);
msg(" Output register (ORA) : %02X\n", ora);
msg(" Output register (ORB) : %02X\n", orb);
msg("Data direction register (DDRA) : %02X\n", ddra);
msg("Data direction register (DDRB) : %02X\n", ddrb);
msg(" Input latching A : %s\n", inputLatchingEnabledA() ? "enabled" : "disabled");
msg(" Input latching B : %s\n", inputLatchingEnabledB() ? "enabled" : "disabled");
msg(" Timer 1 : %d (latched: %d)\n", t1, LO_HI(t1_latch_lo, t1_latch_hi));
msg(" Timer 2 : %d (latched: %d)\n", t2, LO_HI(t2_latch_lo, 0));
msg(" IO memory : ");
for (int j = 0; j < 16; j ++) {
msg("%02X ", io[j]);
}
msg("\n");
}
// -----------------------------------------------------------------------------------------------
// Execution functions
// -----------------------------------------------------------------------------------------------
// One-shot mode timing [F. K.]
// +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
// 02 --+ +-+ +-+ +-+ +-+ +-+ +-#-+ +-+ +-+ +-+ +-+ +-+ +-
// | | |
// +---+ |
// WRITE T1C-H ----+ +-----------------#-------------------------
// ___ | |
// IRQ OUTPUT --------------------------#---------+
// | +---------------
// | |
// PB7 OUTPUT --------+ +---------------
// +-----------------#---------+
// | N |N-1|N-2|N-3| | 0 |N| |N-1|N-2|N-3|
// | |
// |<---- N + 1.5 CYCLES ----->|
void
VIA6522::executeTimer1()
{
if (t1_underflow) {
t1_underflow = false;
setInterruptFlag_T1();
// TODO: PB7 output
// Timer 1 has the special ability to run in free-run mode that reloads automatically
// This generates a continous stream of interrupt events or a square wave on PB7 which
// get inverted every time the timer underflows.
if (freeRunMode1()) {
t1 = HI_LO(t1_latch_hi, t1_latch_lo);
}
return;
}
if (t1) {
// Keep on counting
t1_underflow = (--t1 == 0);
}
}
void
VIA6522::executeTimer2()
{
if (t2_underflow) {
t2_underflow = false;
setInterruptFlag_T2();
// TODO: PB7 output
return;
}
if (t2){
// Keep on counting
t2_underflow = (--t2 == 0);
}
}
bool
VIA6522::IRQ() {
if (io[0xD] /* IFR */ & io[0xE] /* IER */) {
floppy->cpu->setIRQLineVIA();
return true;
} else {
floppy->cpu->clearIRQLineVIA();
return false;
}
}
// -----------------------------------------------------------------------------------------------
// Peek and Poke (Shared behaviour)
// -----------------------------------------------------------------------------------------------
uint8_t
VIA6522::peek(uint16_t addr)
{
assert (addr <= 0xF);
switch(addr) {
case 0x0: assert(0); break; // Not reached. Handled individually by VIA1 and VIA2
case 0x1: assert(0); break; // Not reached. Handled individually by VIA1 and VIA2
case 0x2: // DDRB - Data direction register B
return ddrb;
case 0x3: // DDRA - Data direction register A
return ddra;
case 0x4: // T1 low-order counter
// "8 BITS FROM T1 LOW-ORDER COUNTER TRANSFERRED TO MPU. IN ADDITION T1 INTERRUPT FLAG
// IS RESET (BIT 6 IN INTERRUPT FLAG REGISTER)" [F. K.]
clearInterruptFlag_T1();
floppy->cpu->clearIRQLineVIA();
return LO_BYTE(t1);
case 0x5: // T1 high-order counter
// "8 BITS FROM T1 HIGH-ORDER COUNTER TRANSFERRED TO MPU2" [F. K.]
return HI_BYTE(t1);
case 0x6: // T1 low-order latch
// "8 BITS FROM T1 LOW ORDER-LATCHES TRANSFERRED TO MPU. UNLIKE REG 4 OPERATION,
// THIS DOES NOT CAUSE RESET OF T1 INTERRUPT FLAG" [F. K.]
return t1_latch_lo;
case 0x7: // T1 high-order latch
// "8 BITS FROM T1 HIGH-ORDER LATCHES TRANSFERRED TO MPU
return t1_latch_hi;
case 0x8: // T2 low-order latch/counter
// "8 BITS FROM T2 LOW-ORDER COUNTER TRANSFERRED TO MPU. T2 INTERRUPT FLAG IS RESET" [F. K.]
clearInterruptFlag_T2();
floppy->cpu->clearIRQLineVIA();
return LO_BYTE(t2);
case 0x9: // T2 high-order counter COUNTER TRANSFERRED TO MPU" [F. K.]
// "8 BITS FROM T2 HIGH-ORDER
return HI_BYTE(t2);
case 0xA: // Shift register
clearInterruptFlag_SR();
// warn("peek(0xA): Shift register is not emulated!\n");
break;
case 0xB: // Auxiliary control register
// warn("peek(0xB): Shift register is not emulated!\n");
break;
case 0xC: // Peripheral control register
// TODO
break;
case 0xD: { // IFR - Interrupt Flag Register
// "Bit 7 indicates the status of the IRQ output. This bit corresponds to the logic function:
// IRQ = IFR6xIER6 + IFR5xIER5 + IFR4xIER4 + IFR3xIER3 + IFR2xIER2 + IFR1xIER1 + IFR0xIER0
// x = logic AND, + = logic OR" [F. K.]
io[0xD] &= 0x7F;
uint8_t irq = (io[0xD] /* IFR */ & io[0xE] /* IER */) ? 0x80 : 0x00;
return io[0xD] | irq;
// OLD: return io[addr] | ((io[addr] & io[0x0E]) ? 0x80 : 0x00);
}
case 0xE: // Interrupt enable register
return io[addr] | 0x80; // Bit 7 (set/clear bit) always shows up as 1
case 0xF: assert(0); break; // Not reached. Handled individually by VIA1 and VIA2
}
return io[addr];
}
void VIA6522::poke(uint16_t addr, uint8_t value)
{
assert (addr <= 0x0F);
switch(addr) {
case 0x0: assert(0); break; // Not reached. Handled individually by VIA1 and VIA2
case 0x1: assert(0); break; // Not reached. Handled individually by VIA1 and VIA2
case 0x2: // DDRB - Data direction register B
// "0" ASSOCIATED PB PIN IS AN INPUT (HIGH IMPEDANCE)
// "1" ASSOCIATED PB PIN IS AN OUTPUT WHOSE LEVEL IS DETERMINED BY ORB REGISTER BIT" [F. K.]
ddrb = value;
return;
case 0x3: // DDRB - Data direction register A
// "0" ASSOCIATED PB PIN IS AN INPUT (HIGH IMPEDANCE)
// "1" ASSOCIATED PB PIN IS AN OUTPUT WHOSE LEVEL IS DETERMINED BY ORA REGISTER BIT" [F. K.]
ddra = value;
return;
case 0x4: // T1 low-order counter
// "8 BITS LOADED INTO T1 LOW-ORDER LATCHES. LATCH CONTENTS ARE TRANSFERRED
// INTO LOW-ORDER COUNTER AT THE TIME THE HIGH-ORDER COUNTER IS LOADED (REG 5)" [F. K.]
t1_latch_lo = value;
return;
case 0x5: // T1 high-order counter
// "8 BITS LOADED INTO T1 HIGH-ORDER LATCHES. ALSO AT THIS TIME BOTH HIGH- AND
// LOW-ORDER LATCHES TRANSFERRED INTO T1 COUNTER. T1 INTERRUPT FLAG ALSO IS RESET" [F. K.]
t1_latch_hi = value;
t1 = HI_LO(t1_latch_hi, t1_latch_lo);
clearInterruptFlag_T1();
floppy->cpu->clearIRQLineVIA();
return;
case 0x6: // T1 low-order latct
// "8 BITS LOADED INTO T1 LOW-ORDER LATCHES. THIS OPERATION IS NO DIFFERENT
// THAN A WRITE INTO REG 4" [F. K.]
t1_latch_lo = value;
return;
case 0x7: // T1 high-order latch
// "8 BITS LOADED INTO T1 HIGH-ORDER LATCHES. UNLIKE REG 4 OPERATION NO LATCH TO
// COUNTER TRANSFERS TAKE PLACE" [F. K.]
t1_latch_hi = value;
return;
case 0x8: // T2 low-order latch/counter
// "8 BITS FROM T2 LOW-ORDER COUNTER TRANSFERRED TO MPU. T2 INTERRUPT FLAG IS RESET" [F. K.]
t2_latch_lo = value;
clearInterruptFlag_T2();
floppy->cpu->clearIRQLineVIA();
return;
case 0x9: // T2 high-order counter
// "8 BITS LOADED INTO T2 HIGH-ORDER COUNTER. ALSO, LOW-ORDER LATCH TRANSFERRED
// TO LOW-ORDER COUNTER. IN ADDITION T2 INTERRUPT FLAG IS RESET" [F. K.]
t2 = HI_LO(value, t2_latch_lo);
clearInterruptFlag_T2();
floppy->cpu->clearIRQLineVIA();
return;
case 0xA: // Shift register
clearInterruptFlag_SR();
// warn("poke(0xA,%02X): Shift register is not emulated! (PC = %04X)\n",
// value, c64->cpu->getPC_at_cycle_0());
break;
case 0xB: // Auxiliary control register
// warn("poke(0xB,%02X): Shift register is not emulated! (PC = %04X)\n",
// value, c64->cpu->getPC_at_cycle_0());
break;
case 0xC: // Peripheral control register
break;
case 0xD: // IFR - Interrupt Flag Register
// "... individual flag bits may be cleared by writing a "1" into the appropriate bit of the IFR."
io[addr] &= ~value;
return;
case 0xE: // IER - Interrupt Enable Register
// Bit 7 distinguishes between set and clear
// If bit 7 is 1, each 1 in the provided value will set the corresponding bit
// If bit 7 is 0, each 1 in the provided value will clear the corresponding bit
if (value & 0x80) {
io[addr] |= value & 0x7f;
} else {
io[addr] &= ~value;
}
return;
case 0xF: assert(0); break; // Not reached. Handled individually by VIA1 and VIA2
}
io[addr] = value;
}
// -----------------------------------------------------------------------------------------------
// Peek and Poke (VIA 1)
// -----------------------------------------------------------------------------------------------
uint8_t VIA1::peek(uint16_t addr)
{
switch(addr) {
case 0x0: { // ORB - Output register B
// Clear flags in interrupt flag register (IFR)
clearInterruptFlag_CB1();
if (!CB2selectedAsIndependent())
clearInterruptFlag_CB2();
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
// -----------------------------------------------------------------
// | ATN | Device addr. | ATN | Clock | Clock | Data | Data |
// | in | | out | out | in | out | in |
// Port values (outside the chip)
uint8_t external =
(floppy->iec->getAtnLine() /* 7 */ ? 0x00 : 0x80) |
(floppy->iec->getClockLine() /* 2 */ ? 0x00 : 0x04) |
(floppy->iec->getDataLine() /* 0 */ ? 0x00 : 0x01);
// Determine read value
uint8_t result =
(ddrb & orb) | // Values of bits configured as outputs
(~ddrb & external); // Values of bits configured as inputs
// Set device address to zero
// TODO: Device address is "hard-wired". Set it in "external", above
result &= 0x9F;
return result;
}
case 0x1: // ORA - Output register A
case 0xF:
// Clear flags in interrupt flag register (IFR)
clearInterruptFlag_CA1();
if (!CA2selectedAsIndependent())
clearInterruptFlag_CA2();
// Clean this up ...
floppy->cpu->clearIRQLineATN();
return ora;
default:
return VIA6522::peek(addr);
}
}
void VIA1::poke(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0x0: // ORB - Output register B
// Clear flags in interrupt flag register (IFR)
clearInterruptFlag_CB1();
if (!CB2selectedAsIndependent())
clearInterruptFlag_CB2();
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
// -----------------------------------------------------------------
// | ATN | Device addr. | ATN | Clock | Clock | Data | Data |
// | in | | out | out | in | out | in |
orb = value;
floppy->iec->updateDevicePins(orb, ddrb);
return;
case 0x1: // ORA - Output register A
case 0xF:
// Clear flags in interrupt flag register (IFR)
clearInterruptFlag_CA1();
if (!CA2selectedAsIndependent())
clearInterruptFlag_CA2();
printf("VIA:pokeORA %02X\n", ora);
ora = value;
// Clean this up ...
// floppy->cpu->clearIRQLineATN();
return;
case 0x2:
ddrb = value;
floppy->iec->updateDevicePins(orb, ddrb);
return;
default:
VIA6522::poke(addr, value);
}
}
// -----------------------------------------------------------------------------------------------
// Peek and Poke (VIA 1)
// -----------------------------------------------------------------------------------------------
uint8_t VIA2::peek(uint16_t addr)
{
switch(addr) {
case 0x0: { // ORB - Output register B
// Clear flags in interrupt flag register (IFR)
clearInterruptFlag_CB1();
if (!CB2selectedAsIndependent())
clearInterruptFlag_CB2();
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
// -----------------------------------------------------------------
// | SYNC | Timer control | Write | LED | Rot. | Stepper motor |
// | | (4 disk zones)|protect| | motor | (head move) |
// Collect values on the external port lines
bool SYNC = floppy->getBitAccuracy() ? floppy->getSync() : floppy->getFastLoaderSync();
uint8_t external = (SYNC /* 7 */ ? 0x00 : 0x80) |
(floppy->getLightBarrier() /* 4 */ ? 0x00 : 0x10) |
(floppy->getRedLED() /* 3 */ ? 0x00 : 0x08) |
(floppy->isRotating() /* 2 */ ? 0x00 : 0x04);
uint8_t result =
(ddrb & orb) | // Values of bits configured as outputs
(~ddrb & external); // Values of bits configures as inputs
return result;
}
case 0x1: // ORA - Output register A
case 0xF: {
uint8_t result;
// Clear flags in interrupt flag register (IFR)
clearInterruptFlag_CA1();
if (!CA2selectedAsIndependent())
clearInterruptFlag_CA2();
// If bit accurate emulation is disabled, we perform the read action here ...
if (!floppy->getBitAccuracy())
floppy->fastLoaderRead();
if (inputLatchingEnabledA()) {
// This is the normal operation mode of the drive.
// Every byte that comes from
result =
(ddra & ora) | // Values of bits configured as outputs
(~ddra & ira); // Values of bits configures as inputs
} else {
warn("INPUT LATCHING OF VIA2 IS DISABLED!");
result = 0;
}
if (tracingEnabled()) {
msg("%02X ", ora);
}
// return ora;
return result;
}
case 0x4:
floppy->cpu->clearIRQLineVIA();
return VIA6522::peek(addr);
default:
return VIA6522::peek(addr);
}
}
void VIA2::poke(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0x0: { // ORB - Output register B
// Clear flags in interrupt flag register (IFR)
clearInterruptFlag_CB1();
if (!CB2selectedAsIndependent())
clearInterruptFlag_CB2();
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
// -----------------------------------------------------------------
// | SYNC | Timer control | Write | LED | Rot. | Stepper motor |
// | | (4 disk zones)|protect| | motor | (head move) |
// Disable bits that are not configured as outputs
value &= ddrb;
// Bits 6 and 5
floppy->setZone((value >> 5) & 0x03);
// Bit 3
floppy->setRedLED(GET_BIT(value,3));
// Bit 2
floppy->setRotating(GET_BIT(value,2));
// Bits 1 and 0
if ((orb & 0x03) != (value & 0x03)) {
// A decrease (00-11-10-01-00...) moves the the head down
// An increase (00-01-10-11-00...) moves the head up
if ((value & 0x03) == ((orb+1) & 0x03)) {
floppy->moveHeadUp();
} else if ((value & 0x03) == ((orb-1) & 0x03)) {
floppy->moveHeadDown();
} else {
warn("Unexpected stepper motor control sequence in VC1541 detected\n");
}
}
orb = value;
return;
}
case 0x1: // ORA - Output register A
case 0xF:
// Clear flags in interrupt flag register (IFR)
clearInterruptFlag_CA1();
if (!CA2selectedAsIndependent())
clearInterruptFlag_CA2();
// Hard wired to the data lines of the Gate Array (U10) (read/write head)
// TODO: Take care of ddra
if (tracingEnabled()) {
msg(" W%02X", value);
}
ora = value;
return;
case 0x3:
ddra = value;
if (ddra != 0x00 && ddra != 0xFF) {
debug(1, "Data direction bits of VC1541 contain suspicious values\n");
}
return;
case 0xC:
if (!(io[addr] & 0x20) && (value & 0x20)) {
debug(2, "Switching to read mode mode\n");
}
if ((io[addr] & 0x20) && !(value & 0x20)) {
debug(2, "Switching to write mode\n");
}
io[addr] = value;
return;
default:
VIA6522::poke(addr, value);
}
}
VIA1::VIA1()
{
name = "VIA1";
debug(2, " Creating VIA1 at address %p...\n", this);
}
VIA1::~VIA1()
{
debug(2, " Releasing VIA1...\n");
}
VIA2::VIA2()
{
name = "VIA2";
debug(2, " Creating VIA2 at address %p...\n", this);
}
VIA2::~VIA2()
{
debug(2, " Releasing VIA2...\n");
}
void VIA2::debug0xC() {
uint8_t value = io[0xC];
debug(2,"CA1:\n");
debug(2," %s ACTIVE EDGE\n", (GET_BIT(value,0) ? "POSITIVE" : "NEGATIVE"));
debug(2,"CA2:\n");
switch ((value >> 1) & 0x07) {
case 0: debug(2," INPUT NEG. ACTIVE EDGE\n"); break;
case 1: debug(2," INDEPENDENT INTERRUPT INPUT NEGATIVE EDGE\n"); break;
case 2: debug(2," INPUT POS. ACTIVE EDGE\n"); break;
case 3: debug(2," INDEPENDENT INTERRUPT INPUT POSITIVE EDGE\n"); break;
case 4: debug(2," HANDSHAKE OUTPUT\n"); break;
case 5: debug(2," PULSE OUTPUT\n"); break;
case 6: debug(2," LOW OUTPUT %04X\n", floppy->cpu->getPC_at_cycle_0()); break;
case 7: debug(2," HIGH OUTPUT %04X\n", floppy->cpu->getPC_at_cycle_0()); break;
}
debug(2,"CB1:\n");
debug(2," %s ACTIVE EDGE\n", (GET_BIT(value,4) ? "POSITIVE" : "NEGATIVE"));
debug(2,"CB2:\n");
switch ((value >> 5) & 0x07) {
case 0: debug(2," INPUT NEG. ACTIVE EDGE\n"); break;
case 1: debug(2," INDEPENDENT INTERRUPT INPUT NEGATIVE EDGE\n"); break;
case 2: debug(2," INPUT POS. ACTIVE EDGE\n"); break;
case 3: debug(2," INDEPENDENT INTERRUPT INPUT POSITIVE EDGE\n"); break;
case 4: debug(2," HANDSHAKE OUTPUT\n"); break;
case 5: debug(2," PULSE OUTPUT\n"); break;
case 6: debug(2," LOW OUTPUT\n"); break;
case 7: debug(2," HIGH OUTPUT\n"); break;
}
}