2013-11-04 00:36:15 +00:00
using BizHawk.Common ;
2013-11-13 03:32:25 +00:00
namespace BizHawk.Emulation.Cores.Atari.Atari2600
2012-10-30 22:14:31 +00:00
{
2014-04-02 21:27:14 +00:00
/ *
DPC ( Pitfall 2 )
- - - - -
Back in the day , this game was da shizzle ( and IMO still is ) . It did its trick via
a custom chip in the cartridge . Fortunately for us , there ' s a patent that describes
lots of the internal workings of the chip ( number 4644495 , "video memory system" ) .
Interestingly , the patent shows the DPC as a * separate * device . You plug a
passthrough cartridge into your 2600 , then plug the game cartridge into the
passthrough . Apparently , Activision thought that people wouldn ' t like this , or
there was some other reasoning behind it and they ditched that idea and went with
the DPC inside the cartridge .
Unfortunately for Activision , it was filed in January of 1984 , during the height of
the crash . The inventor is listed as David Crane .
OK , enough background . Now onto the meat :
The DPC chip is just 24 pins , and needs to pass through the chip enable to the
game ROM on the cartridge , so it can only address 2 K of memory . This means the
DPC shows up twice in the address space , once at 1000 - 107F and again at 1800 - 18F F .
There ' s been some discussion about the pitch of the music generated by this chip ,
and how different carts will play the music at different pitches . Turns out , on the
cart , the frequency is determined by a resistor ( 560 K ohms ) and a capacitor integrated
onto the die of the DPC chip itself . The resistor is a 5 % tolerance part , and the
process variations of the DPC itself will control the frequency of the music produced
by it .
If you touch the resistor on the cartridge board , the music pitch will drastically
change , almost like you were playing it on a theremin ! Lowering the resistance makes
the music pitch increase , increasing the resistance makes the pitch lower .
It ' s extremely high impedance so body effects of you touching the pin makes it
vary wildly .
Thus , I say there ' s really no "one true" pitch for the music . The patent , however ,
says that the frequency of this oscillator is 42 KHz in the "preferred embodiment" .
The patent says that it can range from 15 KHz to 80 KHz depending on the application
and the particular design of the sound generator . I chose 21 KHz ( half their preferred
value ) and it sounds fairly close to my actual cartridge .
Address map :
Read Only :
1000 - 1003 : random number generator
1004 - 1005 : sound value ( and MOVAMT value ANDed with draw line carry , with draw line add )
1006 - 1007 : sound value ( and MOVAMT value ANDed with draw line carry , no draw line add )
1008 - 100F : returned data value for fetcher 0 - 7
1010 - 1017 : returned data value for fetcher 0 - 7 , masked
1018 - 101F : returned data value for fetcher 0 - 7 , nybble swapped , masked
1020 - 1027 : returned data value for fetcher 0 - 7 , byte reversed , masked
1028 - 102F : returned data value for fetcher 0 - 7 , rotated right one bit , masked
1030 - 1037 : returned data value for fetcher 0 - 7 , rotated left one bit , masked
1038 - 103F : fetcher 0 - 7 mask
Write Only :
1040 - 1047 : fetcher 0 - 7 start count
1048 - 104F : fetcher 0 - 7 end count
1050 - 1057 : fetcher 0 - 7 pointer low
1058 - 105 B : fetcher 0 - 3 pointer high
105 C : fetcher 4 pointer high and draw line enable
105D - 105F : fetcher 5 - 7 pointer high and music enable
1060 - 1067 : draw line movement value ( MOVAMT )
1068 - 106F : not used
1070 - 1077 : random number generator reset
1078 - 107F : not used
2013-04-20 22:09:19 +00:00
2014-04-02 21:27:14 +00:00
random number generator
- - - - - - - - - - - - - - - - - - - - - - -
2012-10-30 23:13:49 +00:00
2014-04-02 21:27:14 +00:00
The random number generator is used on Pitfall 2 to make the eel flash between white and
black , and nothing else . Failure to emulate this will result in the eel not flashing .
2012-10-30 23:13:49 +00:00
2014-04-02 21:27:14 +00:00
It ' s an 8 bit LFSR which can be reset to the all 0 ' s condition by accessing 1070 - 1077.
Unlike a regular LFSR , this one uses three XOR gates and an inverter , so the illegal
condition is the all 1 ' s condition .
2013-03-11 01:46:12 +00:00
2014-04-02 21:27:14 +00:00
There ' s 255 states and the following code emulates it :
2013-04-20 22:09:19 +00:00
2014-04-02 21:27:14 +00:00
LFSR = ( ( LFSR < < 1 ) | ( ~ ( ( ( LFSR > > 7 ) ^ ( LFSR > > 5 ) ) ^ ( ( LFSR > > 4 ) ^ ( LFSR > > 3 ) ) ) & 1 ) ) & 0xff ;
2013-04-20 22:09:19 +00:00
2014-04-02 21:27:14 +00:00
Bits 3 , 4 , 5 , and 7 are XOR ' d together and inverted and fed back into bit 0 each time the
LFSR is clocked .
2013-04-20 22:09:19 +00:00
2014-04-02 21:27:14 +00:00
The LFSR is clocked each time it is read . It wraps after it is read 255 times . ( The
256 th read returns the same value as the 1 st ) .
2012-10-30 23:13:49 +00:00
2014-04-02 21:27:14 +00:00
data fetchers
- - - - - - - - - - - - -
2013-04-20 22:09:19 +00:00
2014-04-02 21:27:14 +00:00
Internal to the DPC is a 2 K ROM containing the graphics and a few other bits and pieces
( playfield values I think ) of data that can be read via the auto - incrementing data
fetchers .
2013-04-20 22:09:19 +00:00
2014-04-02 21:27:14 +00:00
Each set of 8 addresses ( 1008 - 100F for example ) return the data from one of the 8
data fetcher pointers , returning the data in a slightly different format for each .
The format for the 6 possible register ranges is as follows :
For the byte "ABCDEFGH" ( bit 7 to bit 0 ) it is returned :
1008 - 100F : ABCDEFGH ( never masked )
1010 - 1017 : ABCDEFGH
1018 - 101F : EFGHABCD ( nybble swap )
1020 - 1027 : HGFEDCBA ( bit reversed )
1028 - 102F : 0 ABCDEFG ( shifted right )
1030 - 1037 : BCDEFGH0 ( shifted left )
Reading from each set of locations above returns the byte of data from the DPC ' s
internal ROM . Reading from 1008 accesses data at DF ( data fetcher ) 0 ' s pointer ,
then decrements the pointer . Reading from 1009 accesses data at DF1 , and so on .
There is no difference except how the data is returned when reading from 1008 ,
1010 , 1018 , 1020 , etc . All of them return data pointed to by DF0 ' s pointer . Only
the order of the bits returned changes .
I am not sure what purpose returning the data shifted left or right 1 bit serves ,
and it was not used on Pitfall 2 , but that ' s what it does . I guess you could
use it to make a sprite appear to "wiggle" left and right a bit , if it were 6 pixels
wide .
All of these read ports returns the data masked by an enable signal , except for
1008 - 100F . The data here is never masked . ( more about this in a minute )
To read data out of the chip , first you program in its start address into the
pointer registers . These are at 1050 - 1057 for the lower 8 bits of the pointer
value , and 1058 - 105F for the upper 4 bits of the pointer value . This forms the
12 bit address which can then be used to index the DPC ' s ROM .
A few of the upper bits on 105 C - 105F are used for a few other purposes , which will be
described later .
Masking the data :
- - - - - - - - - - - - - - - - -
1038 - 103F is the readback for the mask value
1040 - 1047 is the start count
1048 - 104F is the end count
2012-10-30 23:13:49 +00:00
2014-04-02 21:27:14 +00:00
The mask value can be read via 1038 - 103F . It returns 0 when graphics are masked , and
FFh when they are not masked . ( 0 = reset , 1 = set )
The basic synopsis is thus :
When the lower 8 bits of the pointer equals the start count , the mask register is set .
When the lower 8 bits of the pointer equals the end count , the mask register is reset .
Writing to the start count register also sets the register .
This allows one to have the sprites only show up on specific scanlines , by programming
the proper start and end counts , and the proper starting value into the pointer . This
way , the sprite can be drawn from top to bottom of the screen , and have it only appear
where it is desired without having to do anything else in the 2600 code .
Making Music :
- - - - - - - - - - - - -
The music is generated by repurposing three of the fetchers , the last three .
Each fetcher can be individually selected for music or fetching .
7 0
- - - - - - - - -
105D - 105F : xxSM PPPP
S : Select clock input to fetching counter . 0 = read pulse when the proper returned
data register is read ( i . e . for fetcher 5 , 1015 is being read ) 1 = music oscillator .
M : Music mode . 1 = enable music mode , 0 = disable music mode .
P : upper 4 bits of the 12 bit data fetcher pointer .
I am not sure why you can separately select the clock source and the music mode ,
but you can . Maybe they had some plans for externally clocking the chip via some
logic to bump the pointers .
Normally you set both the M and P bits to make music .
When in music mode , the lower 8 bits of the fetcher pointer is used as an 8 bit down
counter . Each time the lower 8 bits equals FFh , it is reloaded from the start count
register .
To turn the data fetcher into a square wave generator takes very little hardware . The
start / end count registers are used as - is to toggle the flag register .
This means that the duty cycle of the square waves produced can be varied by adjusting
the end count register relative to the start count register . I suspect the game simply
right shifts the start count by one and stuffs it into the end count to produce a
50 % duty cycle waveform .
The three flag outputs for fetchers 5 to 7 are fed into a cool little circuit composed
of a 3 to 8 decoder and four 4 input NAND gates to produce the 4 bit audio output .
The output is as follows :
fetcher result
567
- - - - - - - - - - - - - - - - - - - - -
000 0 h
001 4 h
010 5 h
011 9 h
100 6 h
101 Ah
110 Bh
111 Fh
This is a somewhat nonlinear mixing of the three channels , so the apparent volume of them
is different relative to each other .
The final 4 bit output value from the above table is then available to read at address
1004 - 1007 , in bits 0 to 3.
Pitfall 2 just reads this location and stuffs it into the audio register every scanline or
so . The value read at 1004 - 1007 is the instantanious value generated by the fetchers and
mixing hardware .
* /
internal class mDPC : MapperBase
{
2014-04-03 19:58:47 +00:00
private ulong totalCycles ;
private ulong elapsedCycles ;
2014-04-02 21:27:14 +00:00
private double FractionalClocks ;
2014-04-03 19:58:47 +00:00
private int bank_4k ;
2014-04-02 21:27:14 +00:00
private IntBuffer Counters = new IntBuffer ( 8 ) ;
private ByteBuffer Flags = new ByteBuffer ( 8 ) ;
private IntBuffer Tops = new IntBuffer ( 8 ) ;
private IntBuffer Bottoms = new IntBuffer ( 8 ) ;
private ByteBuffer DisplayBank_2k = new ByteBuffer ( 2048 ) ;
2014-04-03 19:58:47 +00:00
private byte RandomNumber ;
2014-04-02 21:27:14 +00:00
2014-04-03 19:58:47 +00:00
private bool [ ] MusicMode = new bool [ 3 ] ; // TODO: savestates
2014-04-02 21:27:14 +00:00
public override byte PeekMemory ( ushort addr )
{
return base . PeekMemory ( addr ) ; //TODO
}
public override void ClockCpu ( )
{
totalCycles + + ;
}
public override byte ReadMemory ( ushort addr )
{
ClockRandomNumberGenerator ( ) ;
addr & = 0x0FFF ;
if ( addr < 0x0040 )
2012-10-30 23:13:49 +00:00
{
2014-04-02 21:27:14 +00:00
byte result = 0 ;
int index = addr & 0x07 ;
int function = ( addr > > 3 ) & 0x07 ;
2012-11-01 01:54:33 +00:00
2014-04-02 21:27:14 +00:00
// Update flag register for selected data fetcher
if ( ( Counters [ index ] & 0x00ff ) = = Tops [ index ] )
{
Flags [ index ] = 0xff ;
}
else if ( ( Counters [ index ] & 0x00ff ) = = Bottoms [ index ] )
{
Flags [ index ] = 0x00 ;
}
2012-11-01 01:54:33 +00:00
2014-04-02 21:27:14 +00:00
switch ( function )
2012-11-01 01:54:33 +00:00
{
2014-04-02 21:27:14 +00:00
default :
result = 0 ;
break ;
case 0x00 :
if ( index < 4 )
{
result = RandomNumber ;
}
2014-04-03 19:58:47 +00:00
else // it's a music read
2014-04-02 21:27:14 +00:00
{
byte [ ] MusicAmplitudes = {
0x00 , 0x04 , 0x05 , 0x09 , 0x06 , 0x0a , 0x0b , 0x0f
} ;
2012-11-01 01:54:33 +00:00
2014-04-03 19:58:47 +00:00
// Update the music data fetchers (counter & flag)
2014-04-02 21:27:14 +00:00
UpdateMusicModeDataFetchers ( ) ;
byte i = 0 ;
2014-04-03 19:58:47 +00:00
if ( MusicMode [ 0 ] & & Flags [ 5 ] > 0 )
2012-11-01 01:54:33 +00:00
{
2014-04-02 21:27:14 +00:00
i | = 0x01 ;
2012-11-01 01:54:33 +00:00
}
2014-04-03 19:58:47 +00:00
if ( MusicMode [ 1 ] & & Flags [ 6 ] > 0 )
2012-11-01 01:54:33 +00:00
{
2014-04-02 21:27:14 +00:00
i | = 0x02 ;
2012-11-01 01:54:33 +00:00
}
2014-04-03 19:58:47 +00:00
if ( MusicMode [ 2 ] & & Flags [ 7 ] > 0 )
2012-11-01 01:54:33 +00:00
{
2014-04-02 21:27:14 +00:00
i | = 0x04 ;
2012-11-01 01:54:33 +00:00
}
2014-04-02 21:27:14 +00:00
result = MusicAmplitudes [ i ] ;
}
2014-04-03 19:58:47 +00:00
2014-04-02 21:27:14 +00:00
break ;
case 0x01 :
result = DisplayBank_2k [ 2047 - Counters [ index ] ] ;
break ;
case 0x02 :
result = DisplayBank_2k [ 2047 - ( Counters [ index ] & Flags [ index ] ) ] ;
break ;
case 0x07 :
result = Flags [ index ] ;
break ;
2012-11-01 01:54:33 +00:00
}
2014-04-02 21:27:14 +00:00
// Clock the selected data fetcher's counter if needed
if ( ( index < 5 ) | | ( ( index > = 5 ) & & ( ! MusicMode [ index - 5 ] ) ) )
2012-11-01 01:54:33 +00:00
{
2014-04-02 21:27:14 +00:00
Counters [ index ] = ( Counters [ index ] - 1 ) & 0x07ff ;
2012-11-01 01:54:33 +00:00
}
2014-04-02 21:27:14 +00:00
return result ;
2012-10-30 23:13:49 +00:00
}
2014-04-03 19:58:47 +00:00
Address ( addr ) ;
2014-04-05 14:13:05 +00:00
return Core . Rom [ ( bank_4k < < 12 ) + addr ] ;
2014-04-02 21:27:14 +00:00
}
2012-10-30 23:13:49 +00:00
2014-04-02 21:27:14 +00:00
public override void WriteMemory ( ushort addr , byte value )
{
addr & = 0x0FFF ;
// Clock the random number generator. This should be done for every
// cartridge access, however, we're only doing it for the DPC and
// hot-spot accesses to save time.
ClockRandomNumberGenerator ( ) ;
if ( ( addr > = 0x0040 ) & & ( addr < 0x0080 ) )
2012-10-30 23:13:49 +00:00
{
2014-04-02 21:27:14 +00:00
// Get the index of the data fetcher that's being accessed
int index = addr & 0x07 ;
int function = ( addr > > 3 ) & 0x07 ;
switch ( function )
2012-10-30 23:13:49 +00:00
{
2014-04-02 21:27:14 +00:00
case 0x00 : // DFx top count
Tops [ index ] = value ;
Flags [ index ] = 0x00 ;
break ;
case 0x01 : // DFx bottom count
Bottoms [ index ] = value ;
break ;
case 0x02 : // DFx counter low
if ( ( index > = 5 ) & & MusicMode [ index - 5 ] )
{
Counters [ index ] = ( Counters [ index ] & 0x0700 ) | Tops [ index ] ; // Data fetcher is in music mode so its low counter value should be loaded from the top register not the poked value
}
else
{
// Data fetcher is either not a music mode data fetcher or it
// isn't in music mode so it's low counter value should be loaded
// with the poked value
Counters [ index ] = ( Counters [ index ] & 0x0700 ) | value ;
}
break ;
case 0x03 : // DFx counter high
Counters [ index ] = ( ( value & 0x07 ) < < 8 ) | ( Counters [ index ] & 0x00ff ) ;
// Execute special code for music mode data fetchers
if ( index > = 5 )
{
2014-04-03 19:58:47 +00:00
MusicMode [ index - 5 ] = ( value & 0x10 ) > 0 ;
2014-04-02 21:27:14 +00:00
// NOTE: We are not handling the clock source input for
// the music mode data fetchers. We're going to assume
// they always use the OSC input.
}
2014-04-03 19:58:47 +00:00
2014-04-02 21:27:14 +00:00
break ;
case 0x06 : // Random Number Generator Reset
RandomNumber = 1 ;
break ;
2012-10-30 23:13:49 +00:00
}
}
2014-04-02 21:27:14 +00:00
else
2012-10-30 23:13:49 +00:00
{
2014-04-02 21:27:14 +00:00
Address ( addr ) ;
2012-10-30 23:13:49 +00:00
}
2014-04-03 19:58:47 +00:00
2014-04-02 21:27:14 +00:00
return ;
}
2012-10-30 23:13:49 +00:00
2014-04-02 21:27:14 +00:00
private void Address ( ushort addr )
{
if ( addr = = 0x0FF8 )
2012-10-30 23:13:49 +00:00
{
2014-04-02 21:27:14 +00:00
bank_4k = 0 ;
2012-10-30 23:13:49 +00:00
}
2014-04-02 21:27:14 +00:00
else if ( addr = = 0x0FF9 )
{
bank_4k = 1 ;
}
}
public override void Dispose ( )
{
DisplayBank_2k . Dispose ( ) ;
Counters . Dispose ( ) ;
Flags . Dispose ( ) ;
base . Dispose ( ) ;
}
public override void SyncState ( Serializer ser )
{
2014-04-03 19:58:47 +00:00
// TODO
2014-04-02 21:27:14 +00:00
base . SyncState ( ser ) ;
ser . Sync ( "bank_4k" , ref bank_4k ) ;
ser . Sync ( "DisplayBank_2k" , ref DisplayBank_2k ) ;
ser . Sync ( "Flags" , ref Flags ) ;
ser . Sync ( "Counters" , ref Counters ) ;
ser . Sync ( "RandomNumber" , ref RandomNumber ) ;
}
2012-10-30 23:13:49 +00:00
2014-04-02 21:27:14 +00:00
private void UpdateMusicModeDataFetchers ( )
{
// Calculate the number of cycles since the last update
//int cycles = mySystem->cycles() - mySystemCycles;
//mySystemCycles = mySystem->cycles();
ulong cycles = totalCycles - elapsedCycles ;
elapsedCycles = totalCycles ;
2013-04-20 22:09:19 +00:00
2014-04-02 21:27:14 +00:00
// Calculate the number of DPC OSC clocks since the last update
double clocks = ( ( 20000.0 * cycles ) / 1193191.66666667 ) + FractionalClocks ;
int wholeClocks = ( int ) clocks ;
FractionalClocks = clocks - ( double ) wholeClocks ;
2012-11-01 01:54:33 +00:00
2014-04-02 21:27:14 +00:00
if ( wholeClocks < = 0 )
{
return ;
}
2012-11-01 01:54:33 +00:00
2014-04-02 21:27:14 +00:00
// Let's update counters and flags of the music mode data fetchers
for ( int x = 5 ; x < = 7 ; + + x )
{
// Update only if the data fetcher is in music mode
if ( MusicMode [ x - 5 ] )
2012-11-01 01:54:33 +00:00
{
2014-04-02 21:27:14 +00:00
int top = Tops [ x ] + 1 ;
int newLow = Counters [ x ] & 0x00ff ;
2012-11-01 01:54:33 +00:00
2014-04-02 21:27:14 +00:00
if ( Tops [ x ] ! = 0 )
{
newLow - = ( wholeClocks % top ) ;
if ( newLow < 0 )
2012-11-01 01:54:33 +00:00
{
2014-04-02 21:27:14 +00:00
newLow + = top ;
2012-11-01 01:54:33 +00:00
}
2014-04-02 21:27:14 +00:00
}
else
{
newLow = 0 ;
}
2012-11-01 01:54:33 +00:00
2014-04-02 21:27:14 +00:00
// Update flag register for this data fetcher
if ( newLow < = Bottoms [ x ] )
{
Flags [ x ] = 0x00 ;
}
else if ( newLow < = Tops [ x ] )
{
Flags [ x ] = 0xff ;
2012-11-01 01:54:33 +00:00
}
2014-04-02 21:27:14 +00:00
Counters [ x ] = ( Counters [ x ] & 0x0700 ) | newLow ;
2012-11-01 01:54:33 +00:00
}
}
2014-04-02 21:27:14 +00:00
}
2012-11-01 01:54:33 +00:00
2014-04-02 21:27:14 +00:00
private void ClockRandomNumberGenerator ( )
{
// Table for computing the input bit of the random number generator's
// shift register (it's the NOT of the EOR of four bits)
2014-04-03 19:58:47 +00:00
byte [ ] f =
{
2014-04-02 21:27:14 +00:00
1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1
} ;
// Using bits 7, 5, 4, & 3 of the shift register compute the input
// bit for the shift register
byte bit = f [ ( ( RandomNumber > > 3 ) & 0x07 ) |
( ( RandomNumber & 0x80 ) > 0 ? 0x08 : 0x00 ) ] ;
// Update the shift register
RandomNumber = ( byte ) ( RandomNumber < < 1 | bit ) ;
2012-10-30 22:14:31 +00:00
}
}
}