pi1541/src/m6522.h

385 lines
12 KiB
C
Raw Normal View History

2018-05-20 04:53:34 +00:00
// Pi1541 - A Commodore 1541 disk drive emulator
// Copyright(C) 2018 Stephen White
//
// This file is part of Pi1541.
//
// Pi1541 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 3 of the License, or
// (at your option) any later version.
//
// Pi1541 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 Pi1541. If not, see <http://www.gnu.org/licenses/>.
#ifndef M6522_H
#define M6522_H
#include "IOPort.h"
#include "m6502.h"
class m6522
{
// $1800
// PB 0 data in
// PB 1 data out
// PB 2 clock in
// PB 3 clock out
// PB 4 ATNA out
2018-05-20 04:53:34 +00:00
// PB 5,6 device address
// PB 7,CA1 ATN IN
// $1C00
// PB 0,1 step motor
// PB 2 MTR dirve motor
// PB 3 ACT drive LED
// PB 4 WPS write protect switch
// PB 5,6 bit rate
// PB 7 Sync
// CA 1 Byte ready
// CA 2 SOE set overflow enable 6502
// CB 2 read/write
// IFR
//REG 13 -- INTERRUPT FLAG REGISTER
//+-+-+-+-+-+-+-+-+
//|7|6|5|4|3|2|1|0| SET BY CLEARED BY
//+-+-+-+-+-+-+-+-+ +-----------------------+------------------------------+
// | | | | | | | +--CA2| CA2 ACTIVE EDGE | READ OR WRITE REG 1 (ORA)* |
// | | | | | | | +-----------------------+------------------------------+
// | | | | | | +--CA1--| CA1 ACTIVE EDGE | READ OR WRITE REG 1 (ORA) |
// | | | | | | +-----------------------+------------------------------+
// | | | | | +SHIFT REG| COMPLETE 8 SHIFTS | READ OR WRITE SHIFT REG |
// | | | | | +-----------------------+------------------------------+
// | | | | +-CB2-------| CB2 ACTIVE EDGE | READ OR WRITE ORB* |
// | | | | +-----------------------+------------------------------+
// | | | +-CB1---------| CB1 ACTIVE EDGE | READ OR WRITE ORB |
// | | | +-----------------------+------------------------------+
// | | +-TIMER 2-------| TIME-OUT OF T2 | READ T2 LOW OR WRITE T2 HIGH |
// | | +-----------------------+------------------------------+
// | +-TIMER 1---------| TIME-OUT OF T1 | READ T1 LOW OR WRITE T1 HIGH |
// | +-----------------------+------------------------------+
// +-IRQ---------------| ANY ENABLED INTERRUPT | CLEAR ALL INTERRUPTS |
// +-----------------------+------------------------------+
enum Registers
{
ORB, // 0 Port B
ORA, // 1 Port A
DDRB, // 2 Data direction register for port B
DDRA, // 3 Data direction register for port A
T1CL, // 4 Timer 1 count low
T1CH, // 5 Timer 1 count high
T1LL, // 6 Timer 1 latch low
T1LH, // 7 Timer 1 latch high
T2CL, // 8 Timer 2 count low read-only
T2LL = T2CL, // 8 Timer 2 latch low write-only
T2CH, // 9 Timer 2 count high read/write
SR, // 10 Serial port shift register
ACR, // 11 Auxiliary control register
FCR, // 12 Peripheral control register
IFR, // 13 Interrupt flag register
IER, // 14 Interrupt Enable Register
ORA_NH // 15 Port A with no handshake
};
enum ACR
{
ACR_PA_LATCH_ENABLE = 0x01, // Port A latch
// 0 = disabled
// 1 = enabled on CA1 transition (in)
ACR_PB_LATCH_ENABLE = 0x02, // Port B latch
// 0 = disabled
// 1 = enabled on CB1 transition (in/out)
ACR_SHIFTREG_CTRL = 0x1c, // Shift register control
// 000 = shift reg disabled
// 001 = shift in by timer 2
// 010 = shift in by phi2
// 011 = shift in by external clock (PB6?)
// 100 = free run shift out by timer 2 (keep shifting the same byte out over and over)
// 101 = shift out by timer 2
// 110 = shift out by phi2
// 111 = shift out by external clock(PB6 ? )
ACR_T2_MODE = 0x20, // Timer 2 control
// 0 = one shot (timed interrrupt)
// 1 = count down with pulses on PB6
ACR_T1_MODE = 0x40, // Timer 1 control
// 0 = one shot
// 1 = continuous, i.e. on underflow timer restarts at latch value.
ACR_T1_OUT_PB7 = 0x80 // Output on PB7
};
enum IR
{
IR_CA2 = 0x01, // CA2 flag
// Cleared by a read or write of ORA
IR_CA1 = 0x02, // CA1 flag
// Cleared by a read or write of ORA
IR_SR = 0x04, // Shift Register completion
// 1 at end of 8 shifts
// Cleared by read or write of SR
IR_CB2 = 0x08, // CB2 flag
// Cleared by a read or write of ORB
IR_CB1 = 0x10, // CB1 flag
// Cleared by a read or write of ORB
IR_T2 = 0x20, // Timer 2
// 1 when time out
// 0 after reading T2 low-byte counter or writing T2 high-byte counter
IR_T1 = 0x40, // Timer 1
// 1 when time out
// 0 after reading T1 low-byte counter or writing T1 high-byte latch
IR_IRQ = 0x80 // General interrupt status bit
// 1 if any interrupt active and enabled
// 0 when interrupt condition cleared
};
public:
/*
FCR/PCR
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | |
+----+----+ | +----+----+ |
| | | |
CB2 CONTROL -----+ | | +- CA1 INTERRUPT CONTROL
+-+-+-+------------------------+ | | +--------------------------+
|7|6|5| OPERATION | | | | 0 = NEGATIVE ACTIVE EDGE |
+-+-+-+------------------------+ | | | 1 = POSITIVE ACTIVE EDGE |
|0|0|0| INPUT NEG. ACTIVE EDGE | | | +--------------------------+
+-+-+-+------------------------+ | +---- CA2 INTERRUPT CONTROL
|0|0|1| INDEPENDENT INTERRUPT | | +-+-+-+------------------------+
| | | | INPUT NEGATIVE EDGE | | |3|2|1| OPERATION |
+-+-+-+------------------------+ | +-+-+-+------------------------+
|0|1|0| INPUT POS. ACTIVE EDGE | | |0|0|0| INPUT NEG. ACTIVE EDGE |
+-+-+-+------------------------+ | +-+-+-+------------------------+
|0|1|1| INDEPENDENT INTERRUPT | | |0|0|1| INDEPENDENT INTERRUPT |
| | | | INPUT POSITIVE EDGE | | | | | | INPUT NEGATIVE EDGE |
+-+-+-+------------------------+ | +-+-+-+------------------------+
|1|0|0| HANDSHAKE OUTPUT | | |0|1|0| INPUT POS. ACTIVE EDGE |
+-+-+-+------------------------+ | +-+-+-+------------------------+
|1|0|1| PULSE OUTPUT | | |0|1|1| INDEPENDENT INTERRUPT |
+-+-+-+------------------------+ | | | | | INPUT POSITIVE EDGE |
|1|1|0| LOW OUTPUT | | +-+-+-+------------------------+
+-+-+-+------------------------+ | |1|0|0| HANDSHAKE OUTPUT |
|1|1|1| HIGH OUTPUT | | +-+-+-+------------------------+
+-+-+-+------------------------+ | |1|0|1| PULSE OUTPUT |
CB1 INTERRUPT CONTROL --------+ +-+-+-+------------------------+
+--------------------------+ |1|1|0| LOW OUTPUT |
| 0 = NEGATIVE ACTIVE EDGE | +-+-+-+------------------------+
| 1 = POSITIVE ACTIVE EDGE | |1|1|1| HIGH OUTPUT |
+--------------------------+ +-+-+-+------------------------+
*/
enum FCR
{
FCR_CA1 = 0x01,
FCR_CA2_OUTPUT_MODE0 = 0x02, // 1c00 byte ready active 1541 rom $FAC1
FCR_CA2_OUTPUT_MODE1 = 0x04,
FCR_CA2_EDGE_TRIGGER_MODE = 0x04,
FCR_CA2_IO = 0x08,
FCR_CA2 = 0x0e,
FCR_CB1 = 0x01,
FCR_CB2_OUTPUT_MODE0 = 0x20, // 1c00 writing
FCR_CB2_OUTPUT_MODE1 = 0x40,
FCR_CB2_EDGE_TRIGGER_MODE = 0x40,
FCR_CB2_IO = 0x80,
FCR_CB2 = 0xe0,
};
m6522();
void Reset();
void ConnectIRQ(Interrupt* irq) { this->irq = irq; }
inline IOPort* GetPortA() { return &portA; }
inline bool GetLatchPortA() const { return latchPortA; }
inline unsigned char GetLatchedValueA() { return latchedValueA; }
inline bool GetCA1() { return ca1; }
void InputCA1(bool value);
inline bool GetCA2() { return ca2; }
void InputCA2(bool value);
inline IOPort* GetPortB() { return &portB; }
bool GetLatchPortB() const { return latchPortB; }
unsigned char GetLatchedValueB() { return latchedValueB; }
inline bool GetCB1() { return cb1; }
void InputCB1(bool value);
inline bool GetCB2() { return cb2; }
void InputCB2(bool value);
void Execute();
unsigned char Read(unsigned int address);
unsigned char Peek(unsigned int address);
void Write(unsigned int address, unsigned char value);
inline unsigned char GetFCR()
{
return functionControlRegister;
}
private:
inline unsigned char ReadPortB()
{
unsigned char ddr = portB.GetDirection();
unsigned char value = (latchPortB && (interruptFlagRegister & (unsigned char)IR_CB1) != 0) ? latchedValueB : (unsigned char)((portB.GetInput() & ~ddr) | (portB.GetOutput() & ddr));
ClearInterrupt(IR_CB1 | IR_CB2);
return value;
}
inline void WritePortB(unsigned char value)
{
ClearInterrupt(IR_CB1 | IR_CB2);
if ((functionControlRegister & (unsigned char)(FCR_CB2_IO | FCR_CB2_OUTPUT_MODE1)) == (unsigned char)(FCR_CB2_IO | FCR_CB2_OUTPUT_MODE1))
cb2 = false;
portB.SetOutput(value);
}
inline unsigned char ReadPortA(bool handshake)
{
unsigned char ddr = portA.GetDirection();
unsigned char value = (latchPortA && (interruptFlagRegister & (unsigned char)IR_CA1) != 0) ? latchedValueA : (unsigned char)((portA.GetInput() & ~ddr) | (portA.GetOutput() & ddr));
if (handshake)
ClearInterrupt(IR_CA1 | IR_CA2);
return value;
}
inline unsigned char PeekPortA()
{
unsigned char ddr = portA.GetDirection();
unsigned char value = (latchPortA && (interruptFlagRegister & (unsigned char)IR_CA1) != 0) ? latchedValueA : (unsigned char)((portA.GetInput() & ~ddr) | (portA.GetOutput() & ddr));
return value;
}
inline void WritePortA(unsigned char value, bool handshake)
{
if (handshake)
{
ClearInterrupt(IR_CA1 | IR_CA2);
if ((functionControlRegister & (unsigned char)(FCR_CA2_IO | FCR_CA2_OUTPUT_MODE1)) == (unsigned char)(FCR_CA2_IO | FCR_CA2_OUTPUT_MODE1))
ca2 = false;
}
portA.SetOutput(value);
}
inline unsigned char PeekPortB()
{
unsigned char ddr = portB.GetDirection();
unsigned char value = (latchPortB && (interruptFlagRegister & (unsigned char)IR_CB1) != 0) ? latchedValueB : (unsigned char)((portB.GetInput() & ~ddr) | (portB.GetOutput() & ddr));
return value;
}
inline void SetInterrupt(unsigned char flag)
{
if (!(interruptFlagRegister & flag))
{
interruptFlagRegister |= flag;
OutputIRQ();
}
}
inline void ClearInterrupt(unsigned char flag)
{
if (interruptFlagRegister & flag)
{
interruptFlagRegister &= ~flag;
OutputIRQ();
}
}
inline void OutputIRQ()
{
if (interruptEnabledRegister & interruptFlagRegister & 0x7f)
{
if ((interruptFlagRegister & IR_IRQ) == 0)
{
interruptFlagRegister |= IR_IRQ;
if (irq) irq->Assert();
}
}
else
{
if (interruptFlagRegister & IR_IRQ)
{
interruptFlagRegister &= ~IR_IRQ;
if (irq) irq->Release();
}
}
}
struct Counter
{
union
{
unsigned short value;
struct
{
// if porting to big endian, swap these.
unsigned char l;
unsigned char h;
} bytes;
};
};
Interrupt* irq;
unsigned char functionControlRegister;
unsigned char auxiliaryControlRegister;
IOPort portA;
bool latchPortA;
unsigned char latchedValueA;
bool ca1;
bool ca2;
bool pulseCA2;
IOPort portB;
bool latchPortB;
unsigned char latchedValueB;
bool cb1;
bool cb1Old;
bool cb2;
bool pulseCB2;
Counter t1c;
Counter t1l;
bool t1Ticking;
bool t1Reload;
bool t1OutPB7;
bool t1FreeRun;
bool t1FreeRunIRQsOn;
bool t1TimedOut;
bool t1_pb7;
bool t1OneShotTriggeredIRQ;
Counter t2c;
unsigned char t2Latch;
bool t2Reload;
bool t2CountingDown;
bool t2CountingPB6ModeOld;
bool t2CountingPB6Mode;
bool t2TimedOut;
bool t2LowTimedOut;
bool t2OneShotTriggeredIRQ;
unsigned t2TimedOutCount;
unsigned char pb6Old;
unsigned char interruptFlagRegister;
unsigned char interruptEnabledRegister;
unsigned char shiftRegister;
unsigned bitsShiftedSoFar;
unsigned cb1OutputShiftClock;
unsigned char cb2Shift; // version of cb2 controlled by the shift register
bool cb1OutputShiftClockPositiveEdge;
};
#endif