mirror of https://github.com/stella-emu/stella.git
Cleaned up the RIOT class a little more, hopefully making it easier to
understand. Eliminated certains constants that I could never really explain (like the 0x40000 constant from z26), and more properly emulated the behaviour when going negative on the timer count (according to the documentation). git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@1483 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
parent
0682a85999
commit
74362c3f0f
|
@ -13,7 +13,7 @@
|
||||||
// See the file "license" for information on usage and redistribution of
|
// See the file "license" for information on usage and redistribution of
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
//
|
//
|
||||||
// $Id: M6532.cxx,v 1.20 2008-04-20 19:52:33 stephena Exp $
|
// $Id: M6532.cxx,v 1.21 2008-04-23 16:51:11 stephena Exp $
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
@ -55,15 +55,14 @@ void M6532::reset()
|
||||||
myTimer = 25 + (random.next() % 75);
|
myTimer = 25 + (random.next() % 75);
|
||||||
myIntervalShift = 6;
|
myIntervalShift = 6;
|
||||||
myCyclesWhenTimerSet = 0;
|
myCyclesWhenTimerSet = 0;
|
||||||
myTimerReadAfterInterrupt = false;
|
myInterruptEnabled = false;
|
||||||
|
myInterruptTriggered = false;
|
||||||
|
|
||||||
// Zero the I/O registers
|
// Zero the I/O registers
|
||||||
myOutA = 0x00;
|
myDDRA = myDDRB = myOutA = 0x00;
|
||||||
myDDRA = 0x00;
|
|
||||||
myDDRB = 0x00;
|
|
||||||
|
|
||||||
// Zero the timer registers
|
// Zero the timer registers
|
||||||
myOutTimer[0] = myOutTimer[1] = myOutTimer[2] = myOutTimer[3] = 0x0;
|
myOutTimer[0] = myOutTimer[1] = myOutTimer[2] = myOutTimer[3] = 0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -161,30 +160,26 @@ uInt8 M6532::peek(uInt16 addr)
|
||||||
case 0x04: // Timer Output
|
case 0x04: // Timer Output
|
||||||
case 0x06:
|
case 0x06:
|
||||||
{
|
{
|
||||||
uInt32 timer = timerClocks();
|
myInterruptTriggered = false;
|
||||||
|
Int32 timer = timerClocks();
|
||||||
|
|
||||||
// See if the timer has expired yet?
|
if(timer >= 0)
|
||||||
// Note that this constant comes from z26, and corresponds to
|
|
||||||
// 256 intervals of T1024T (ie, the maximum that the timer
|
|
||||||
// should hold)
|
|
||||||
if(!(timer & 0x40000))
|
|
||||||
{
|
{
|
||||||
return (timer >> myIntervalShift) & 0xff;
|
return (uInt8)(timer >> myIntervalShift);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
myTimerReadAfterInterrupt = true;
|
if(timer != -1)
|
||||||
return timer & 0xff;
|
myInterruptTriggered = true;
|
||||||
|
|
||||||
|
return (uInt8)(timer >= -256 ? timer : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x05: // Interrupt Flag
|
case 0x05: // Interrupt Flag
|
||||||
case 0x07:
|
case 0x07:
|
||||||
{
|
{
|
||||||
// TODO - test this
|
if((timerClocks() >= 0) || myInterruptEnabled && myInterruptTriggered)
|
||||||
// it seems as if myTimerReadAfterInterrupt being true
|
|
||||||
// would mean that the interrupt has been enabled??
|
|
||||||
if((timerClocks() >= 0) || myTimerReadAfterInterrupt)
|
|
||||||
return 0x00;
|
return 0x00;
|
||||||
else
|
else
|
||||||
return 0x80;
|
return 0x80;
|
||||||
|
@ -210,122 +205,93 @@ void M6532::poke(uInt16 addr, uInt8 value)
|
||||||
if((addr & 0x1080) == 0x0080 && (addr & 0x0200) == 0x0000)
|
if((addr & 0x1080) == 0x0080 && (addr & 0x0200) == 0x0000)
|
||||||
{
|
{
|
||||||
myRAM[addr & 0x007f] = value;
|
myRAM[addr & 0x007f] = value;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if((addr & 0x07) == 0x00) // Port A I/O Register (Joystick)
|
|
||||||
{
|
|
||||||
myOutA = value;
|
|
||||||
uInt8 a = myOutA & myDDRA;
|
|
||||||
|
|
||||||
Controller& port0 = myConsole.controller(Controller::Left);
|
// A2 Distingusishes I/O registers from the timer
|
||||||
port0.write(Controller::One, a & 0x10);
|
if((addr & 0x04) != 0)
|
||||||
port0.write(Controller::Two, a & 0x20);
|
|
||||||
port0.write(Controller::Three, a & 0x40);
|
|
||||||
port0.write(Controller::Four, a & 0x80);
|
|
||||||
|
|
||||||
Controller& port1 = myConsole.controller(Controller::Right);
|
|
||||||
port1.write(Controller::One, a & 0x01);
|
|
||||||
port1.write(Controller::Two, a & 0x02);
|
|
||||||
port1.write(Controller::Three, a & 0x04);
|
|
||||||
port1.write(Controller::Four, a & 0x08);
|
|
||||||
}
|
|
||||||
else if((addr & 0x07) == 0x01) // Port A Data Direction Register
|
|
||||||
{
|
{
|
||||||
myDDRA = value;
|
if((addr & 0x10) != 0)
|
||||||
|
{
|
||||||
uInt8 a = myOutA | ~myDDRA;
|
myInterruptEnabled = (addr & 0x08);
|
||||||
|
setTimerRegister(value, addr & 0x03);
|
||||||
// TODO - Fix this properly in the core
|
}
|
||||||
// Any time the core code needs to know what type of controller
|
|
||||||
// is connected, it's by definition a bug
|
|
||||||
// A real Atari doesn't 'know' that an AVox is connected, so we
|
|
||||||
// shouldn't either
|
|
||||||
/*
|
|
||||||
20060608 bkw: Not the most elegant thing in the world...
|
|
||||||
When a bit in the DDR is set as input, +5V is placed on its output
|
|
||||||
pin. When it's set as output, either +5V or 0V (depending on the
|
|
||||||
contents of SWCHA) will be placed on the output pin.
|
|
||||||
The standard macros for the AtariVox use this fact to send data
|
|
||||||
to the port.
|
|
||||||
|
|
||||||
This code isn't 100% correct: it assumes the SWCHA bits are all 0.
|
|
||||||
This is good enough to emulate the AtariVox, if the programmer is
|
|
||||||
using SWACNT to do output (e.g. the SPKOUT macro from speakjet.inc)
|
|
||||||
and if he's leaving SWCHA alone.
|
|
||||||
|
|
||||||
The inaccuracy here means that wrongly-written code will still
|
|
||||||
be able to drive the emulated AtariVox, even though it wouldn't
|
|
||||||
work on real hardware.
|
|
||||||
*/
|
|
||||||
Controller& port0 = myConsole.controller(Controller::Left);
|
|
||||||
port0.write(Controller::One, a & 0x10);
|
|
||||||
port0.write(Controller::Two, a & 0x20);
|
|
||||||
port0.write(Controller::Three, a & 0x40);
|
|
||||||
port0.write(Controller::Four, a & 0x80);
|
|
||||||
|
|
||||||
Controller& port1 = myConsole.controller(Controller::Right);
|
|
||||||
port1.write(Controller::One, a & 0x01);
|
|
||||||
port1.write(Controller::Two, a & 0x02);
|
|
||||||
port1.write(Controller::Three, a & 0x04);
|
|
||||||
port1.write(Controller::Four, a & 0x08);
|
|
||||||
}
|
|
||||||
else if((addr & 0x07) == 0x02) // Port B I/O Register (Console switches)
|
|
||||||
{
|
|
||||||
return; // hardwired as read-only
|
|
||||||
}
|
|
||||||
else if((addr & 0x07) == 0x03) // Port B Data Direction Register
|
|
||||||
{
|
|
||||||
return; // hardwired as read-only
|
|
||||||
}
|
|
||||||
else if((addr & 0x17) == 0x14) // Write timer divide by 1
|
|
||||||
{
|
|
||||||
myOutTimer[0] = value;
|
|
||||||
myIntervalShift = 0;
|
|
||||||
myTimer = myOutTimer[0] << myIntervalShift;
|
|
||||||
myCyclesWhenTimerSet = mySystem->cycles();
|
|
||||||
myTimerReadAfterInterrupt = false;
|
|
||||||
}
|
|
||||||
else if((addr & 0x17) == 0x15) // Write timer divide by 8
|
|
||||||
{
|
|
||||||
myOutTimer[1] = value;
|
|
||||||
myIntervalShift = 3;
|
|
||||||
myTimer = myOutTimer[1] << myIntervalShift;
|
|
||||||
myCyclesWhenTimerSet = mySystem->cycles();
|
|
||||||
myTimerReadAfterInterrupt = false;
|
|
||||||
}
|
|
||||||
else if((addr & 0x17) == 0x16) // Write timer divide by 64
|
|
||||||
{
|
|
||||||
myOutTimer[2] = value;
|
|
||||||
myIntervalShift = 6;
|
|
||||||
myTimer = myOutTimer[2] << myIntervalShift;
|
|
||||||
myCyclesWhenTimerSet = mySystem->cycles();
|
|
||||||
myTimerReadAfterInterrupt = false;
|
|
||||||
}
|
|
||||||
else if((addr & 0x17) == 0x17) // Write timer divide by 1024
|
|
||||||
{
|
|
||||||
myOutTimer[3] = value;
|
|
||||||
myIntervalShift = 10;
|
|
||||||
myTimer = myOutTimer[3] << myIntervalShift;
|
|
||||||
myCyclesWhenTimerSet = mySystem->cycles();
|
|
||||||
myTimerReadAfterInterrupt = false;
|
|
||||||
}
|
|
||||||
else if((addr & 0x14) == 0x04) // Write Edge Detect Control
|
|
||||||
{
|
|
||||||
#ifdef DEBUG_ACCESSES
|
|
||||||
cerr << "M6532 Poke (Write Edge Detect): "
|
|
||||||
<< ((addr & 0x02) ? "PA7 enabled" : "PA7 disabled")
|
|
||||||
<< ", "
|
|
||||||
<< ((addr & 0x01) ? "Positive edge" : "Negative edge")
|
|
||||||
<< endl;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_ACCESSES
|
switch(addr & 0x03)
|
||||||
cerr << "BAD M6532 Poke: " << hex << addr << endl;
|
{
|
||||||
#endif
|
case 0: // Port A I/O Register (Joystick)
|
||||||
|
{
|
||||||
|
myOutA = value;
|
||||||
|
uInt8 a = myOutA & myDDRA;
|
||||||
|
|
||||||
|
Controller& port0 = myConsole.controller(Controller::Left);
|
||||||
|
port0.write(Controller::One, a & 0x10);
|
||||||
|
port0.write(Controller::Two, a & 0x20);
|
||||||
|
port0.write(Controller::Three, a & 0x40);
|
||||||
|
port0.write(Controller::Four, a & 0x80);
|
||||||
|
|
||||||
|
Controller& port1 = myConsole.controller(Controller::Right);
|
||||||
|
port1.write(Controller::One, a & 0x01);
|
||||||
|
port1.write(Controller::Two, a & 0x02);
|
||||||
|
port1.write(Controller::Three, a & 0x04);
|
||||||
|
port1.write(Controller::Four, a & 0x08);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: // Port A Data Direction Register
|
||||||
|
{
|
||||||
|
myDDRA = value;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Undocumented feature used by the AtariVox and SaveKey.
|
||||||
|
When the DDR is changed, the ORA (output register A) cached
|
||||||
|
from a previous write to SWCHA is 'applied' to the controller pins.
|
||||||
|
|
||||||
|
When a bit in the DDR is set as input, +5V is placed on its output
|
||||||
|
pin. When it's set as output, either +5V or 0V (depending on the
|
||||||
|
contents of SWCHA) will be placed on the output pin.
|
||||||
|
The standard macros for the AtariVox use this fact to send data
|
||||||
|
to the port. This is represented by the following algorithm:
|
||||||
|
|
||||||
|
if(DDR bit is input) set output as 1
|
||||||
|
else if(DDR bit is output) set output as bit in ORA
|
||||||
|
*/
|
||||||
|
uInt8 a = myOutA | ~myDDRA;
|
||||||
|
|
||||||
|
Controller& port0 = myConsole.controller(Controller::Left);
|
||||||
|
port0.write(Controller::One, a & 0x10);
|
||||||
|
port0.write(Controller::Two, a & 0x20);
|
||||||
|
port0.write(Controller::Three, a & 0x40);
|
||||||
|
port0.write(Controller::Four, a & 0x80);
|
||||||
|
|
||||||
|
Controller& port1 = myConsole.controller(Controller::Right);
|
||||||
|
port1.write(Controller::One, a & 0x01);
|
||||||
|
port1.write(Controller::Two, a & 0x02);
|
||||||
|
port1.write(Controller::Three, a & 0x04);
|
||||||
|
port1.write(Controller::Four, a & 0x08);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: // Port B I/O & DDR Registers (Console switches)
|
||||||
|
break; // hardwired as read-only
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void M6532::setTimerRegister(uInt8 value, uInt8 interval)
|
||||||
|
{
|
||||||
|
static const uInt8 shift[] = { 0, 3, 6, 10 };
|
||||||
|
|
||||||
|
myInterruptTriggered = false;
|
||||||
|
myIntervalShift = shift[interval];
|
||||||
|
myOutTimer[interval] = value;
|
||||||
|
myTimer = value << myIntervalShift;
|
||||||
|
myCyclesWhenTimerSet = mySystem->cycles();
|
||||||
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
bool M6532::save(Serializer& out) const
|
bool M6532::save(Serializer& out) const
|
||||||
{
|
{
|
||||||
|
@ -343,10 +309,12 @@ bool M6532::save(Serializer& out) const
|
||||||
out.putInt(myTimer);
|
out.putInt(myTimer);
|
||||||
out.putInt(myIntervalShift);
|
out.putInt(myIntervalShift);
|
||||||
out.putInt(myCyclesWhenTimerSet);
|
out.putInt(myCyclesWhenTimerSet);
|
||||||
out.putBool(myTimerReadAfterInterrupt);
|
out.putBool(myInterruptEnabled);
|
||||||
out.putByte((char)myOutA);
|
out.putBool(myInterruptTriggered);
|
||||||
|
|
||||||
out.putByte((char)myDDRA);
|
out.putByte((char)myDDRA);
|
||||||
out.putByte((char)myDDRB);
|
out.putByte((char)myDDRB);
|
||||||
|
out.putByte((char)myOutA);
|
||||||
out.putByte((char)myOutTimer[0]);
|
out.putByte((char)myOutTimer[0]);
|
||||||
out.putByte((char)myOutTimer[1]);
|
out.putByte((char)myOutTimer[1]);
|
||||||
out.putByte((char)myOutTimer[2]);
|
out.putByte((char)myOutTimer[2]);
|
||||||
|
@ -384,12 +352,12 @@ bool M6532::load(Deserializer& in)
|
||||||
myTimer = (uInt32) in.getInt();
|
myTimer = (uInt32) in.getInt();
|
||||||
myIntervalShift = (uInt32) in.getInt();
|
myIntervalShift = (uInt32) in.getInt();
|
||||||
myCyclesWhenTimerSet = (uInt32) in.getInt();
|
myCyclesWhenTimerSet = (uInt32) in.getInt();
|
||||||
myTimerReadAfterInterrupt = in.getBool();
|
myInterruptEnabled = in.getBool();
|
||||||
|
myInterruptTriggered = in.getBool();
|
||||||
|
|
||||||
myOutA = (uInt8) in.getByte();
|
|
||||||
myDDRA = (uInt8) in.getByte();
|
myDDRA = (uInt8) in.getByte();
|
||||||
myDDRB = (uInt8) in.getByte();
|
myDDRB = (uInt8) in.getByte();
|
||||||
|
myOutA = (uInt8) in.getByte();
|
||||||
myOutTimer[0] = (uInt8) in.getByte();
|
myOutTimer[0] = (uInt8) in.getByte();
|
||||||
myOutTimer[1] = (uInt8) in.getByte();
|
myOutTimer[1] = (uInt8) in.getByte();
|
||||||
myOutTimer[2] = (uInt8) in.getByte();
|
myOutTimer[2] = (uInt8) in.getByte();
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// See the file "license" for information on usage and redistribution of
|
// See the file "license" for information on usage and redistribution of
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
//
|
//
|
||||||
// $Id: M6532.hxx,v 1.11 2008-04-20 19:52:33 stephena Exp $
|
// $Id: M6532.hxx,v 1.12 2008-04-23 16:51:11 stephena Exp $
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
||||||
#ifndef M6532_HXX
|
#ifndef M6532_HXX
|
||||||
|
@ -32,7 +32,7 @@ class Deserializer;
|
||||||
RIOT
|
RIOT
|
||||||
|
|
||||||
@author Bradford W. Mott
|
@author Bradford W. Mott
|
||||||
@version $Id: M6532.hxx,v 1.11 2008-04-20 19:52:33 stephena Exp $
|
@version $Id: M6532.hxx,v 1.12 2008-04-23 16:51:11 stephena Exp $
|
||||||
*/
|
*/
|
||||||
class M6532 : public Device
|
class M6532 : public Device
|
||||||
{
|
{
|
||||||
|
@ -130,6 +130,8 @@ class M6532 : public Device
|
||||||
inline Int32 timerClocks()
|
inline Int32 timerClocks()
|
||||||
{ return myTimer - (mySystem->cycles() - myCyclesWhenTimerSet); }
|
{ return myTimer - (mySystem->cycles() - myCyclesWhenTimerSet); }
|
||||||
|
|
||||||
|
void setTimerRegister(uInt8 data, uInt8 interval);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Reference to the console
|
// Reference to the console
|
||||||
const Console& myConsole;
|
const Console& myConsole;
|
||||||
|
@ -146,11 +148,11 @@ class M6532 : public Device
|
||||||
// Indicates the number of cycles when the timer was last set
|
// Indicates the number of cycles when the timer was last set
|
||||||
Int32 myCyclesWhenTimerSet;
|
Int32 myCyclesWhenTimerSet;
|
||||||
|
|
||||||
// Indicates if a read from timer has taken place after interrupt occured
|
// Indicates if a timer interrupt has been enabled
|
||||||
bool myTimerReadAfterInterrupt;
|
bool myInterruptEnabled;
|
||||||
|
|
||||||
// Last value written to Port A
|
// Indicates if a read from timer has taken place after interrupt occured
|
||||||
uInt8 myOutA;
|
bool myInterruptTriggered;
|
||||||
|
|
||||||
// Data Direction Register for Port A
|
// Data Direction Register for Port A
|
||||||
uInt8 myDDRA;
|
uInt8 myDDRA;
|
||||||
|
@ -158,6 +160,9 @@ class M6532 : public Device
|
||||||
// Data Direction Register for Port B
|
// Data Direction Register for Port B
|
||||||
uInt8 myDDRB;
|
uInt8 myDDRB;
|
||||||
|
|
||||||
|
// Last value written to Port A
|
||||||
|
uInt8 myOutA;
|
||||||
|
|
||||||
// Last value written to the timer registers
|
// Last value written to the timer registers
|
||||||
uInt8 myOutTimer[4];
|
uInt8 myOutTimer[4];
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue