in cores/atari2600/stella/src/emucore/TIA.cxx [1373:2186]
bool TIA::poke(uInt16 addr, uInt8 value)
{
addr = addr & 0x003f;
Int32 clock = mySystem->cycles() * 3;
Int16 delay = TIATables::PokeDelay[addr];
// See if this is a poke to a PF register
if(delay == -1)
{
static uInt32 d[4] = {4, 5, 2, 3};
Int32 x = ((clock - myClockWhenFrameStarted) % 228);
delay = d[(x / 3) & 3];
}
// Update frame to current CPU cycle before we make any changes!
updateFrame(clock + delay);
// If a VSYNC hasn't been generated in time go ahead and end the frame
if(((clock - myClockWhenFrameStarted) / 228) >= (Int32)myMaximumNumberOfScanlines)
{
mySystem->m6502().stop();
myPartialFrameFlag = false;
}
switch(addr)
{
case VSYNC: // Vertical sync set-clear
{
myVSYNC = value;
if(myVSYNC & 0x02)
{
// Indicate when VSYNC should be finished. This should really
// be 3 * 228 according to Atari's documentation, however, some
// games don't supply the full 3 scanlines of VSYNC.
myVSYNCFinishClock = clock + 228;
}
else if(!(myVSYNC & 0x02) && (clock >= myVSYNCFinishClock))
{
// We're no longer interested in myVSYNCFinishClock
myVSYNCFinishClock = 0x7FFFFFFF;
// Since we're finished with the frame tell the processor to halt
mySystem->m6502().stop();
myPartialFrameFlag = false;
}
break;
}
case VBLANK: // Vertical blank set-clear
{
// Is the dump to ground path being set for I0, I1, I2, and I3?
if(!(myVBLANK & 0x80) && (value & 0x80))
{
myDumpEnabled = true;
}
// Is the dump to ground path being removed from I0, I1, I2, and I3?
else if((myVBLANK & 0x80) && !(value & 0x80))
{
myDumpEnabled = false;
myDumpDisabledCycle = mySystem->cycles();
}
// Are the latches for I4 and I5 being reset?
if (!(myVBLANK & 0x40))
myINPT4 = myINPT5 = 0x80;
// Check for the first scanline at which VBLANK is disabled.
// Usually, this will be the first scanline to start drawing.
if(myStartScanline == 0 && !(value & 0x10))
myStartScanline = scanlines();
myVBLANK = value;
break;
}
case WSYNC: // Wait for leading edge of HBLANK
{
// It appears that the 6507 only halts during a read cycle so
// we test here for follow-on writes which should be ignored as
// far as halting the processor is concerned.
//
// TODO - 08-30-2006: This halting isn't correct since it's
// still halting on the original write. The 6507 emulation
// should be expanded to include a READY line.
if(mySystem->m6502().lastAccessWasRead())
{
// Tell the cpu to waste the necessary amount of time
waitHorizontalSync();
}
break;
}
case RSYNC: // Reset horizontal sync counter
{
waitHorizontalRSync();
break;
}
case NUSIZ0: // Number-size of player-missle 0
{
// TODO - 08-11-2009: determine correct delay instead of always
// using '8' in TIATables::PokeDelay
updateFrame(clock + 8);
myNUSIZ0 = value;
mySuppressP0 = 0;
break;
}
case NUSIZ1: // Number-size of player-missle 1
{
// TODO - 08-11-2009: determine correct delay instead of always
// using '8' in TIATables::PokeDelay
updateFrame(clock + 8);
myNUSIZ1 = value;
mySuppressP1 = 0;
break;
}
case COLUP0: // Color-Luminance Player 0
{
uInt8 color = value & 0xfe;
if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
color |= 0x01;
myColor[P0Color] = myColor[M0Color] = color;
break;
}
case COLUP1: // Color-Luminance Player 1
{
uInt8 color = value & 0xfe;
if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
color |= 0x01;
myColor[P1Color] = myColor[M1Color] = color;
break;
}
case COLUPF: // Color-Luminance Playfield
{
uInt8 color = value & 0xfe;
if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
color |= 0x01;
myColor[PFColor] = myColor[BLColor] = color;
break;
}
case COLUBK: // Color-Luminance Background
{
uInt8 color = value & 0xfe;
if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
color |= 0x01;
myColor[BKColor] = color;
break;
}
case CTRLPF: // Control Playfield, Ball size, Collisions
{
myCTRLPF = value;
// The playfield priority and score bits from the control register
// are accessed when the frame is being drawn. We precompute the
// necessary value here so we can save time while drawing.
myPlayfieldPriorityAndScore = ((myCTRLPF & 0x06) << 5);
// Update the playfield mask based on reflection state if
// we're still on the left hand side of the playfield
if(((clock - myClockWhenFrameStarted) % 228) < (68 + 79))
myPFMask = TIATables::PFMask[myCTRLPF & 0x01];
break;
}
case REFP0: // Reflect Player 0
{
// See if the reflection state of the player is being changed
if(((value & 0x08) && !myREFP0) || (!(value & 0x08) && myREFP0))
{
myREFP0 = (value & 0x08);
myCurrentGRP0 = TIATables::GRPReflect[myCurrentGRP0];
}
break;
}
case REFP1: // Reflect Player 1
{
// See if the reflection state of the player is being changed
if(((value & 0x08) && !myREFP1) || (!(value & 0x08) && myREFP1))
{
myREFP1 = (value & 0x08);
myCurrentGRP1 = TIATables::GRPReflect[myCurrentGRP1];
}
break;
}
case PF0: // Playfield register byte 0
{
myPF = (myPF & 0x000FFFF0) | ((value >> 4) & 0x0F);
if(myPF == 0)
myEnabledObjects &= ~PFBit;
else
myEnabledObjects |= PFBit;
#ifdef DEBUGGER_SUPPORT
uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
if(dataAddr)
mySystem->setAccessFlags(dataAddr, CartDebug::PGFX);
#endif
break;
}
case PF1: // Playfield register byte 1
{
myPF = (myPF & 0x000FF00F) | ((uInt32)value << 4);
if(myPF == 0)
myEnabledObjects &= ~PFBit;
else
myEnabledObjects |= PFBit;
#ifdef DEBUGGER_SUPPORT
uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
if(dataAddr)
mySystem->setAccessFlags(dataAddr, CartDebug::PGFX);
#endif
break;
}
case PF2: // Playfield register byte 2
{
myPF = (myPF & 0x00000FFF) | ((uInt32)value << 12);
if(myPF == 0)
myEnabledObjects &= ~PFBit;
else
myEnabledObjects |= PFBit;
#ifdef DEBUGGER_SUPPORT
uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
if(dataAddr)
mySystem->setAccessFlags(dataAddr, CartDebug::PGFX);
#endif
break;
}
case RESP0: // Reset Player 0
{
Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
Int16 newx;
// Check if HMOVE is currently active
if(myCurrentHMOVEPos != 0x7FFFFFFF)
{
newx = hpos < 7 ? 3 : ((hpos + 5) % 160);
// If HMOVE is active, adjust for any remaining horizontal move clocks
applyActiveHMOVEMotion(hpos, newx, myMotionClockP0);
}
else
{
newx = hpos < -2 ? 3 : ((hpos + 5) % 160);
applyPreviousHMOVEMotion(hpos, newx, myHMP0);
}
if(myPOSP0 != newx)
{
// TODO - update player timing
// Find out under what condition the player is being reset
delay = TIATables::PxPosResetWhen[myNUSIZ0 & 7][myPOSP0][newx];
switch(delay)
{
// Player is being reset during the display of one of its copies
case 1:
// TODO - 08-20-2009: determine whether we really need to update
// the frame here, and also come up with a way to eliminate the
// 200KB PxPosResetWhen table.
updateFrame(clock + 11);
mySuppressP0 = 1;
break;
// Player is being reset in neither the delay nor display section
case 0:
mySuppressP0 = 1;
break;
// Player is being reset during the delay section of one of its copies
case -1:
mySuppressP0 = 0;
break;
}
myPOSP0 = newx;
}
break;
}
case RESP1: // Reset Player 1
{
Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
Int16 newx;
// Check if HMOVE is currently active
if(myCurrentHMOVEPos != 0x7FFFFFFF)
{
newx = hpos < 7 ? 3 : ((hpos + 5) % 160);
// If HMOVE is active, adjust for any remaining horizontal move clocks
applyActiveHMOVEMotion(hpos, newx, myMotionClockP1);
}
else
{
newx = hpos < -2 ? 3 : ((hpos + 5) % 160);
applyPreviousHMOVEMotion(hpos, newx, myHMP1);
}
if(myPOSP1 != newx)
{
// TODO - update player timing
// Find out under what condition the player is being reset
delay = TIATables::PxPosResetWhen[myNUSIZ1 & 7][myPOSP1][newx];
switch(delay)
{
// Player is being reset during the display of one of its copies
case 1:
// TODO - 08-20-2009: determine whether we really need to update
// the frame here, and also come up with a way to eliminate the
// 200KB PxPosResetWhen table.
updateFrame(clock + 11);
mySuppressP1 = 1;
break;
// Player is being reset in neither the delay nor display section
case 0:
mySuppressP1 = 1;
break;
// Player is being reset during the delay section of one of its copies
case -1:
mySuppressP1 = 0;
break;
}
myPOSP1 = newx;
}
break;
}
case RESM0: // Reset Missle 0
{
Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
Int16 newx;
// Check if HMOVE is currently active
if(myCurrentHMOVEPos != 0x7FFFFFFF)
{
newx = hpos < 7 ? 2 : ((hpos + 4) % 160);
// If HMOVE is active, adjust for any remaining horizontal move clocks
applyActiveHMOVEMotion(hpos, newx, myMotionClockM0);
}
else
{
newx = hpos < -1 ? 2 : ((hpos + 4) % 160);
applyPreviousHMOVEMotion(hpos, newx, myHMM0);
}
if(newx != myPOSM0)
{
myPOSM0 = newx;
}
break;
}
case RESM1: // Reset Missle 1
{
Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
Int16 newx;
// Check if HMOVE is currently active
if(myCurrentHMOVEPos != 0x7FFFFFFF)
{
newx = hpos < 7 ? 2 : ((hpos + 4) % 160);
// If HMOVE is active, adjust for any remaining horizontal move clocks
applyActiveHMOVEMotion(hpos, newx, myMotionClockM1);
}
else
{
newx = hpos < -1 ? 2 : ((hpos + 4) % 160);
applyPreviousHMOVEMotion(hpos, newx, myHMM1);
}
if(newx != myPOSM1)
{
myPOSM1 = newx;
}
break;
}
case RESBL: // Reset Ball
{
Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
// Check if HMOVE is currently active
if(myCurrentHMOVEPos != 0x7FFFFFFF)
{
myPOSBL = hpos < 7 ? 2 : ((hpos + 4) % 160);
// If HMOVE is active, adjust for any remaining horizontal move clocks
applyActiveHMOVEMotion(hpos, myPOSBL, myMotionClockBL);
}
else
{
myPOSBL = hpos < 0 ? 2 : ((hpos + 4) % 160);
applyPreviousHMOVEMotion(hpos, myPOSBL, myHMBL);
}
break;
}
case AUDC0: // Audio control 0
{
myAUDC0 = value & 0x0f;
mySound.set(addr, value, mySystem->cycles());
break;
}
case AUDC1: // Audio control 1
{
myAUDC1 = value & 0x0f;
mySound.set(addr, value, mySystem->cycles());
break;
}
case AUDF0: // Audio frequency 0
{
myAUDF0 = value & 0x1f;
mySound.set(addr, value, mySystem->cycles());
break;
}
case AUDF1: // Audio frequency 1
{
myAUDF1 = value & 0x1f;
mySound.set(addr, value, mySystem->cycles());
break;
}
case AUDV0: // Audio volume 0
{
myAUDV0 = value & 0x0f;
mySound.set(addr, value, mySystem->cycles());
break;
}
case AUDV1: // Audio volume 1
{
myAUDV1 = value & 0x0f;
mySound.set(addr, value, mySystem->cycles());
break;
}
case GRP0: // Graphics Player 0
{
// Set player 0 graphics
myGRP0 = value;
// Copy player 1 graphics into its delayed register
myDGRP1 = myGRP1;
// Get the "current" data for GRP0 base on delay register and reflect
uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
myCurrentGRP0 = myREFP0 ? TIATables::GRPReflect[grp0] : grp0;
// Get the "current" data for GRP1 base on delay register and reflect
uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
myCurrentGRP1 = myREFP1 ? TIATables::GRPReflect[grp1] : grp1;
// Set enabled object bits
if(myCurrentGRP0 != 0)
myEnabledObjects |= P0Bit;
else
myEnabledObjects &= ~P0Bit;
if(myCurrentGRP1 != 0)
myEnabledObjects |= P1Bit;
else
myEnabledObjects &= ~P1Bit;
#ifdef DEBUGGER_SUPPORT
uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
if(dataAddr)
mySystem->setAccessFlags(dataAddr, CartDebug::GFX);
#endif
break;
}
case GRP1: // Graphics Player 1
{
// Set player 1 graphics
myGRP1 = value;
// Copy player 0 graphics into its delayed register
myDGRP0 = myGRP0;
// Copy ball graphics into its delayed register
myDENABL = myENABL;
// Get the "current" data for GRP0 base on delay register
uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
myCurrentGRP0 = myREFP0 ? TIATables::GRPReflect[grp0] : grp0;
// Get the "current" data for GRP1 base on delay register
uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
myCurrentGRP1 = myREFP1 ? TIATables::GRPReflect[grp1] : grp1;
// Set enabled object bits
if(myCurrentGRP0 != 0)
myEnabledObjects |= P0Bit;
else
myEnabledObjects &= ~P0Bit;
if(myCurrentGRP1 != 0)
myEnabledObjects |= P1Bit;
else
myEnabledObjects &= ~P1Bit;
if(myVDELBL ? myDENABL : myENABL)
myEnabledObjects |= BLBit;
else
myEnabledObjects &= ~BLBit;
#ifdef DEBUGGER_SUPPORT
uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
if(dataAddr)
mySystem->setAccessFlags(dataAddr, CartDebug::GFX);
#endif
break;
}
case ENAM0: // Enable Missile 0 graphics
{
myENAM0 = value & 0x02;
if(myENAM0 && !myRESMP0)
myEnabledObjects |= M0Bit;
else
myEnabledObjects &= ~M0Bit;
break;
}
case ENAM1: // Enable Missile 1 graphics
{
myENAM1 = value & 0x02;
if(myENAM1 && !myRESMP1)
myEnabledObjects |= M1Bit;
else
myEnabledObjects &= ~M1Bit;
break;
}
case ENABL: // Enable Ball graphics
{
myENABL = value & 0x02;
if(myVDELBL ? myDENABL : myENABL)
myEnabledObjects |= BLBit;
else
myEnabledObjects &= ~BLBit;
break;
}
case HMP0: // Horizontal Motion Player 0
{
pokeHMP0(value, clock);
break;
}
case HMP1: // Horizontal Motion Player 1
{
pokeHMP1(value, clock);
break;
}
case HMM0: // Horizontal Motion Missle 0
{
pokeHMM0(value, clock);
break;
}
case HMM1: // Horizontal Motion Missle 1
{
pokeHMM1(value, clock);
break;
}
case HMBL: // Horizontal Motion Ball
{
pokeHMBL(value, clock);
break;
}
case VDELP0: // Vertical Delay Player 0
{
myVDELP0 = value & 0x01;
uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
myCurrentGRP0 = myREFP0 ? TIATables::GRPReflect[grp0] : grp0;
if(myCurrentGRP0 != 0)
myEnabledObjects |= P0Bit;
else
myEnabledObjects &= ~P0Bit;
break;
}
case VDELP1: // Vertical Delay Player 1
{
myVDELP1 = value & 0x01;
uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
myCurrentGRP1 = myREFP1 ? TIATables::GRPReflect[grp1] : grp1;
if(myCurrentGRP1 != 0)
myEnabledObjects |= P1Bit;
else
myEnabledObjects &= ~P1Bit;
break;
}
case VDELBL: // Vertical Delay Ball
{
myVDELBL = value & 0x01;
if(myVDELBL ? myDENABL : myENABL)
myEnabledObjects |= BLBit;
else
myEnabledObjects &= ~BLBit;
break;
}
case RESMP0: // Reset missle 0 to player 0
{
if(myRESMP0 && !(value & 0x02))
{
uInt16 middle = 4;
switch(myNUSIZ0 & 0x07)
{
// 1-pixel delay is taken care of in TIATables::PxMask
case 0x05: middle = 8; break; // double size
case 0x07: middle = 16; break; // quad size
}
myPOSM0 = myPOSP0 + middle;
if(myCurrentHMOVEPos != 0x7FFFFFFF)
{
myPOSM0 -= (8 - myMotionClockP0);
myPOSM0 += (8 - myMotionClockM0);
}
CLAMP_POS(myPOSM0);
}
myRESMP0 = value & 0x02;
if(myENAM0 && !myRESMP0)
myEnabledObjects |= M0Bit;
else
myEnabledObjects &= ~M0Bit;
break;
}
case RESMP1: // Reset missle 1 to player 1
{
if(myRESMP1 && !(value & 0x02))
{
uInt16 middle = 4;
switch(myNUSIZ1 & 0x07)
{
// 1-pixel delay is taken care of in TIATables::PxMask
case 0x05: middle = 8; break; // double size
case 0x07: middle = 16; break; // quad size
}
myPOSM1 = myPOSP1 + middle;
if(myCurrentHMOVEPos != 0x7FFFFFFF)
{
myPOSM1 -= (8 - myMotionClockP1);
myPOSM1 += (8 - myMotionClockM1);
}
CLAMP_POS(myPOSM1);
}
myRESMP1 = value & 0x02;
if(myENAM1 && !myRESMP1)
myEnabledObjects |= M1Bit;
else
myEnabledObjects &= ~M1Bit;
break;
}
case HMOVE: // Apply horizontal motion
{
int hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
myCurrentHMOVEPos = hpos;
// See if we need to enable the HMOVE blank bug
myHMOVEBlankEnabled = myAllowHMOVEBlanks ?
TIATables::HMOVEBlankEnableCycles[((clock - myClockWhenFrameStarted) % 228) / 3] : false;
// Do we have to undo some of the already applied cycles from an
// active graphics latch?
if(hpos + HBLANK < 17 * 4)
{
Int16 cycle_fix = 17 - ((hpos + HBLANK + 7) / 4);
if(myHMP0mmr) myPOSP0 = (myPOSP0 + cycle_fix) % 160;
if(myHMP1mmr) myPOSP1 = (myPOSP1 + cycle_fix) % 160;
if(myHMM0mmr) myPOSM0 = (myPOSM0 + cycle_fix) % 160;
if(myHMM1mmr) myPOSM1 = (myPOSM1 + cycle_fix) % 160;
if(myHMBLmmr) myPOSBL = (myPOSBL + cycle_fix) % 160;
}
myHMP0mmr = myHMP1mmr = myHMM0mmr = myHMM1mmr = myHMBLmmr = false;
// Can HMOVE activities be ignored?
if(hpos >= -5 && hpos < 97 )
{
myMotionClockP0 = 0;
myMotionClockP1 = 0;
myMotionClockM0 = 0;
myMotionClockM1 = 0;
myMotionClockBL = 0;
myHMOVEBlankEnabled = false;
myCurrentHMOVEPos = 0x7FFFFFFF;
break;
}
myMotionClockP0 = (myHMP0 ^ 0x80) >> 4;
myMotionClockP1 = (myHMP1 ^ 0x80) >> 4;
myMotionClockM0 = (myHMM0 ^ 0x80) >> 4;
myMotionClockM1 = (myHMM1 ^ 0x80) >> 4;
myMotionClockBL = (myHMBL ^ 0x80) >> 4;
// Adjust number of graphics motion clocks for active display
if(hpos >= 97 && hpos < 151)
{
Int16 skip_motclks = (160 - myCurrentHMOVEPos - 6) >> 2;
myMotionClockP0 -= skip_motclks;
myMotionClockP1 -= skip_motclks;
myMotionClockM0 -= skip_motclks;
myMotionClockM1 -= skip_motclks;
myMotionClockBL -= skip_motclks;
if(myMotionClockP0 < 0) myMotionClockP0 = 0;
if(myMotionClockP1 < 0) myMotionClockP1 = 0;
if(myMotionClockM0 < 0) myMotionClockM0 = 0;
if(myMotionClockM1 < 0) myMotionClockM1 = 0;
if(myMotionClockBL < 0) myMotionClockBL = 0;
}
if(hpos >= -56 && hpos < -5)
{
Int16 max_motclks = (7 - (myCurrentHMOVEPos + 5)) >> 2;
if(myMotionClockP0 > max_motclks) myMotionClockP0 = max_motclks;
if(myMotionClockP1 > max_motclks) myMotionClockP1 = max_motclks;
if(myMotionClockM0 > max_motclks) myMotionClockM0 = max_motclks;
if(myMotionClockM1 > max_motclks) myMotionClockM1 = max_motclks;
if(myMotionClockBL > max_motclks) myMotionClockBL = max_motclks;
}
// Apply horizontal motion
if(hpos < -5 || hpos >= 157)
{
myPOSP0 += 8 - myMotionClockP0;
myPOSP1 += 8 - myMotionClockP1;
myPOSM0 += 8 - myMotionClockM0;
myPOSM1 += 8 - myMotionClockM1;
myPOSBL += 8 - myMotionClockBL;
}
// Make sure positions are in range
CLAMP_POS(myPOSP0);
CLAMP_POS(myPOSP1);
CLAMP_POS(myPOSM0);
CLAMP_POS(myPOSM1);
CLAMP_POS(myPOSBL);
// TODO - handle late HMOVE's
mySuppressP0 = mySuppressP1 = 0;
break;
}
case HMCLR: // Clear horizontal motion registers
{
pokeHMP0(0, clock);
pokeHMP1(0, clock);
pokeHMM0(0, clock);
pokeHMM1(0, clock);
pokeHMBL(0, clock);
break;
}
case CXCLR: // Clear collision latches
{
myCollision = 0;
break;
}
default:
{
#ifdef DEBUG_ACCESSES
cerr << "BAD TIA Poke: " << hex << addr << endl;
#endif
break;
}
}
return true;
}