Mostly Inclusive Atari 2600 Mapper / Selected Hardware Document --------------------------------------------------------------- 03/04/12 Kevin Horton aka kevtris --- Version 1.00 Things covered in this document: -------------------------------- "original" mappers from when the system was in production: 2K 4K CV (Commavid) F8 (Atari 8K) F6 (Atari 16K) F4 (Atari 32K) FE (Activision 8K) E0 (Parker bros. 8K) 3F (Tigervision 8K) FA (CBS RAM Plus 12K) E7 (M-network 16K) F0 (Megaboy 64K) UA (UA Ltd 8K) "homebrew" mappers which are more recent: 3F (Enhanced Tigervision style, up to 512K) 3E (up to 512K of ROM, 256K of RAM) 0840 (Econobanking) MC (Megacart) EF (16K) X07 (Atariage) 4A50 (no name) Atari 2600 peripherals: Spectravideo Compumate Pitfall 2 DPC Supercharger Supercharger tape format Supercharger demo unit --- "original" 2600 Mappers ----------------------- 2K (no name) ----- 2K of ROM. no bankswitching. 4K (no name) ----- 4K of ROM. no bankswitching. CV Commavid ----- This was used by Commavid. It allowed for both ROM and RAM on the cartridge, without using bankswitching. There's 2K of ROM and 1K of RAM. 2K of ROM is mapped at 1800-1FFF. 1K of RAM is mapped in at 1000-17FF. The read port is at 1000-13FF. The write port is at 1400-17FF. F8 (Atari style 8K) ----- This is the fairly standard way 8K of cartridge ROM was implemented. There are two 4K ROM banks, which get mapped into the 4K of cartridge space. Accessing 1FF8 or 1FF9 selects one of the two 4K banks. When one of these two addresses are accessed, the banks switch spontaniously. ANY kind of access will trigger the switching- reading or writing. Usually games use LDA or BIT on 1FF8/1FF9 to perform the switch. When the switch occurs, the entire 4K ROM bank switches, including the code that is reading the 1FF8/1FF9 location. Usually, games put a small stub of code in BOTH banks so when the switch occurs, the code won't crash. F6 (Atari style 16K) ----- This is a fairly standard 16K bankswitching method. It works like F8, except there's four 4K banks of ROM, selected by accessing 1FF6 through 1FF9. These sequentially select one of the 4 banks. i.e. 1FF6 selects bank 0, 1FF7 selects bank 1, etc. F4 (Atari style 32K) ----- Again, this works like F8 and F6 except now there's 8 4K banks. Selection is performed by accessing 1FF4 through 1FFB. FE (Activision special) ----- Activision used this method on only three games: Decathlon, Robot Tank, and the prototype Thwocker. This mapper is one of the more interesting ones in that it uses stack access to select banks. It is composed of two 4K banks, similar to F8. Unlike F8, however, switching occurs when the stack is accessed. This mapper allows for "automatic" bankswitching to occur, using JSR and RTS. The addresses for all JSRs and RTS' are either Fxxx or Dxxx, and the mapper uses this to figure out which bank it should be going to. The cycles of a JSR are as such: 1: opcode fetch 2: fetch low byte of address 3: read 100,s : garbage fetch 4: write 100,s : PCH, decrement S 5: write 100,s : PCL, decrement S 6: fetch high byte of address The cycles of an RTS are as such: 1: opcode fetch 2: fetch next opcode (and throw it away) 3: read 100,S : increment S 4: read 100,S : pull PCL from stack, increment S 5: read 100,S : pull PCH from stack The chip can determine what instruction is being executed by watching the data and address bus. It watches for 20 (JSR) and 60 (RTS), and accesses to 100-1ff: (opcode cycles) 20 (opcode) add low (new add low) stack (garbage read) stack (push PCH) stack (push PCL) add high (new add hi) : latch D5. This is the NEW bank we need to be in. 60 (opcode) xx (garbage fetch) stack stack (pull PCL) stack (pull PCH) : latch D5. This is the NEW bank we need to be in. Using emulators or similar there is a large cheat that can be used. A13 can be used to simply select which 8K bank to be in. E0 (Parker Bros) ----- Parker Brothers used this, and it was used on one other game (Tooth Protectors). It uses 8K of ROM and can map 1K sections of it. This mapper has 4 1K banks of ROM in the address space. The address space is broken up into the following locations: 1000-13FF : To select a 1K ROM bank here, access 1FE0-1FE7 (1FE0 = select first 1K, etc) 1400-17FF : To select a 1K ROM bank, access 1FE8-1FEF 1800-1BFF : To select a 1K ROM bank, access 1FF0-1FF7 1C00-1FFF : This is fixed to the last 1K ROM bank of the 8K Like F8, F6, etc. accessing one of the locations indicated will perform the switch. 3F (Tigervision) ----- Traditionally, this method was used on the Tigervision games. The ROMs were all 8K, and there's two 2K pages in the 4K of address space. The upper bank is fixed to the last 2K of the ROM. The first 2K is selectable by writing to any location between 0000 and 003F. Yes, this overlaps the TIA, but this is not a big deal. You simply use the mirrors of the TIA at 40-7F instead! To select a bank, the games write to 3Fh, because it's not implemented on the TIA. The homebrew community has decided that if 8K is good, more ROM is better! This mapper can support up to 512K bytes of ROM just by implementing all 8 bits on the mapper register, and this has been done... however I do not think 512K ROMs have been made just yet. FA (RAM Plus) ----- CBS Thought they'd throw a few tricks of their own at the 2600 with this. It's got 12K of ROM and 256 bytes of RAM. This works similar to F8, except there's only 3 4K ROM banks. The banks are selected by accessing 1FF8, 1FF9, and 1FFA. There's also 256 bytes of RAM mapped into 1000-11FF. The write port is at 1000-10FF, and the read port is 1100-11FF. E7 (M-Network) ----- M-network wanted something of their own too, so they came up with what they called "Big Game" (this was printed on the prototype ASICs on the prototype carts). It can handle up to 16K of ROM and 2K of RAM. 1000-17FF is selectable 1800-19FF is RAM 1A00-1FFF is fixed to the last 1.5K of ROM Accessing 1FE0 through 1FE6 selects bank 0 through bank 6 of the ROM into 1000-17FF. Accessing 1FE7 enables 1K of the 2K RAM, instead. When the RAM is enabled, this 1K appears at 1000-17FF. 1000-13FF is the write port, 1400-17FF is the read port. 1800-19FF also holds RAM. 1800-18FF is the write port, 1900-19FF is the read port. Only 256 bytes of RAM is accessable at time, but there are four different 256 byte banks making a total of 1K accessable here. Accessing 1FE8 through 1FEB select which 256 byte bank shows up. F0 (Megaboy) ----- This was used on one game, "megaboy".. Some kind of educational cartridge. It supports 64K of ROM making it the biggest single production game made during the original run of the 2600. Bankswitching is very simple. There's 16 4K banks, and accessing 1FF0 causes the bank number to increment. This means that you must keep accessing 1FF0 until the bank you want is selected. Each bank is numbered by means of one of the ROM locations, and the code simply keeps accessing 1FF0 until the bank it is looking for comes up. UA (UA Ltd) ----- This one was found out later on, lurking on a proto of Pleaides. It works with 8K of ROM and banks it in 4K at a time. Accessing 0220 will select the first bank, and accessing 0240 will select the second. That's about it for the "Traditional" mappers that were used on commercial releases. ----------------------------------- The 2600 programming community's been pretty busy with adding new mappers. Here's the list of known (to me) mappers used on homebrew games. 3E (Boulderdash ----- This works similar to 3F (Tigervision) above, except RAM has been added. The range of addresses has been restricted, too. Only 3E and 3F can be written to now. 1000-17FF - this bank is selectable 1800-1FFF - this bank is the last 2K of the ROM To select a particular 2K ROM bank, its number is poked into address 3F. Because there's 8 bits, there's enough for 256 2K banks, or a maximum of 512K of ROM. Writing to 3E, however, is what's new. Writing here selects a 1K RAM bank into 1000-17FF. The example (Boulderdash) uses 16K of RAM, however there's theoretically enough space for 256K of RAM. When RAM is selected, 1000-13FF is the read port while 1400-17FF is the write port. 0840 (Econobanking) ----- This is another 8K bankswitching method with two 4K banks. The rationale is that it's cheap and easy to implement with only a single 74HC153 or 253 dual 4:1 multiplexer. This multiplexer can act as a 1 bit latch AND the inverter for A12. To bankswitch, the following mask it used: A13 A0 ---------------- 0 1xxx xBxx xxxx Each bit corresponds to one of the 13 address lines. a 0 or 1 means that bit must be 0 or 1 to trigger the bankswitch. x is a bit that is not concidered (it can be either 0 or 1 and is thus a "don't care" bit). B is the bank we will select. sooo, accessing 0800 will select bank 0, and 0840 will select bank 1. SB (Superbanking) ----- This is the same as 0840, except A0-A6 are used instead of just A6, as so: A13 A0 ---------------- 0 1xxx xBBB BBBB By using 6 bits, the maximum ROM size has been increased from 8K to 256K. MC (Megacart) ----- This is the mapper for the "Chris Wilkson's Megacart". Only four addresses are used to bankswitch on this one. Up to 128K of ROM and 64K of RAM can be accessed. 1000-13FF is selected by address 3C 1400-17FF is selected by address 3D 1800-1BFF is selected by address 3E 1C00-1FFF is selected by address 3F The value written determines what will be selected: 00-7F written will select one of the 128 1K ROM banks 80-FF written will select one of the 128 512 byte RAM banks When a RAM bank is selected, the lower 512 bytes is the write port, while the upper 512 bytes is the read port. On accessing address FFFC or FFFD, the last 1K bank points to the last bank in ROM, to allow for system initialization. Jumping out of the last bank disables this. It's debatable how easy this system would be to implement on a real system. Detecting when to disable the last bank fixing is difficult. The documentation says: " Megacart Specification, Rev1.1 (c) 1997 Chris Wilkson cwilkson@mit.edu Because the console's memory is randomized at powerup, there is no way to predict the data initially contained in the "hot addresses". Therefore, hardware will force slot 3 to always point to ROM block $FF immediately after any read or write to the RESET vector at $FFFC-$FFFD. Block $FF must contain code to initialize the 4 memory slots to point to the desired physical memory blocks before any other code can be executed. After program execution jumps out of the boot code, the hardware will release slot 3 and it will function just like any other slot. " Unfortunately, there's not an easy way to detect this. Just watching the address bus won't work easily: Writing anywhere outside the bank 1C00-1FFF (i.e. bank registers, RAM, TIA registers) will cause the switching to revert bank 3, crashing the system. The only way I can see it working is to disregard any access to addresses 3C-3F. Emulators have it easier: they can simply watch the program counter, vs. the address bus. An actual system doesn't have that luxury, unfortunately, so it must disregard accesses to 3C-3F instead. EF (no name?) ----- This is a fairly simple method that allows for up to 64K of ROM, using 16 4K banks. It works similar to F8, F6, etc. Only the addresses to perform the switch is 1FE0-1FEF. Accessing one of these will select the desired bank. 1FE0 = bank 0, 1FE1 = bank 1, etc. X07 (Atariage) ----- Apparently, this was only used on one cart: Stella's Stocking. Similar to EF, there are 16 4K banks, for a total of up to 64K of ROM. The addresses to select banks is below the ROM area, however. The following TWO masks are used: A13 A0 ---------------- 0 1xxx nnnn 1101 This means the address 80B selects bank 0, 81B selects bank 1, etc. In addition to this, there is another way: A13 A0 ---------------- 0 0xxx 0nxx xxxx This is somewhat special purpose: Accessing here does nothing, unless one of the last two banks are selected (banks 14 or 15). In that case, the new bank is: 111n i.e. accessing 0000 will select bank 14 (Eh, 1110b) while accessing 0040 will select bank 15 (Fh, 1111b). This allows for bankswitching by accessing TIA registers at 00-3F or 40-7F without incurring any overhead. 4A50 (no name) ----- Upon review, I don't think this method is terribly workable on real hardware. There's so many problems that I kinda gave up trying to count them all. Seems that this is more of a "pony" method than something actually usable. ("pony" referring to "I want this, and that, and that, and a pony too!") One major problem is that it specifies that memory can be read and written to at the same address, but this is nearly impossible to detect on a 2600 cartridge. You'd almost have to try and figure out what opcodes are being run, and what cycle it's on somehow, all just by watching address and data bus state. Not very practical. The other problem is just the sheer volume of things it is supposed to do. There's just tons and tons of unnecessary things like attempting to detect BIT instructions, handling page wraps and other silly things. This all supposidly fit into a Xilinx XC9536XL but I am not sure how the chip could handle the RAM issue above at all. It almost needs to see R/W and M2 (clock) to be able to properly do most of the things it's doing. ---------------------------------------------- Peripherals ----------- Spectravideo Compumate Add-on ----- This is more than just a cartridge mapper- it's also a "computer" add-on. There's two 8K EPROMs soldered on top of each other. There's two short wires with DB-9's on them which you plug into the two controller ports. A 42 or so key membrane keyboard with audio in and audio out, and 1K of RAM. Port A on the RIOT is used to run most of the functions on the Compumate: 7 0 --------- ACRE 31BB A - Audio input from tape player C - Audio out to tape player and 4017 CLK R - 4017 RST, and RAM direction. (high = write, low = read) E - RAM enable. 1 = disable RAM, 0 = enable RAM 3 - Row 3 of keyboard 1 - Row 1 of keyboard B - 2 bit ROM bank number All bits are outputs except for the 2 row inputs from the keyboard. Unlike most things, the Compumate uses all three of the TIA inputs on each joystick port (paddles and fire). TIA inputs: 0 - function key 1 - pulled high thru 20K resistor 2 - pulled high thru 20K resistor 3 - shift key 4 - Row 0 5 - Row 2 Memory Map: ----------- 1000-1FFF : selectable 4K ROM bank (selected by D0, D1 on portA) On powerup, the port is all 1's, so the last bank of ROM is enabled, RAM is disabled. when RAM is enabled: 1000-17FF : 2K of RAM. It's mapped into 1000-17FF. Unlike most 2600 carts, bit 5 of portA controls if the RAM is readable or writable. When it's high, the RAM is write only. When it's low, it is read only. There's no separate read and write ports. Keyboard: --------- The keyboard's composed of a 4017 1 of 10 counter, driving the 10 columns of the keyboard. It has 4 rows. The 4 row outputs are buffered by inverters. Bit 5 of portA controls the reset line on the 4017. Pulling it high will reset scanning to column 0. Pulling it low will allow the counter to be clocked. Bit 6 of portA clocks the 4017. Each rising edge advances the column one count. There's 10 columns labelled 0-9, and 4 rows, labelled 0-3. Column 0 1 2 3 4 5 6 7 8 9 +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ | 7 | | 6 | | 8 | | 2 | | 3 | | 0 | | 9 | | 5 | | 1 | | 4 | 0 +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ | U | | Y | | I | | W | | E | | P | | O | | T | | Q | | R | 1 +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ Row +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ | J | | H | | K | | S | | D | |ent| | L | | G | | A | | F | 2 +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ | M | | N | | < | | X | | C | |spc| | > | | B | | Z | | V | 3 +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ Function and Shift are separate keys that are read by 2 of the paddle inputs. These two buttons pull the specific paddle input low when pressed. Because the inputs are inverted, a low indicates a pressed button, and a high is an unpressed one. The audio input/output are designed to drive a tape player. The audio output is buffered through an inverter and 2 resistors and a capacitor to reduce the level to feed it into the tape player. The audio input is passed through a .1uf capacitor and is pulled to 1/2 supply by two 20K resistors, then it goes through a hex inverting schmitt trigger to square it up. This then runs into bit 7 of portA. 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 2K of memory. This means the DPC shows up twice in the address space, once at 1000-107F and again at 1800-18FF. 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 (560K 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 42KHz in the "preferred embodiment". The patent says that it can range from 15KHz to 80KHz depending on the application and the particular design of the sound generator. I chose 21KHz (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-105B : fetcher 0-3 pointer high 105C : 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 random number generator ----------------------- 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. 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. There's 255 states and the following code emulates it: LFSR = ((LFSR << 1) | (~(((LFSR >> 7) ^ (LFSR >> 5)) ^ ((LFSR >> 4) ^ (LFSR >> 3))) & 1)) & 0xff; Bits 3, 4, 5, and 7 are XOR'd together and inverted and fed back into bit 0 each time the LFSR is clocked. The LFSR is clocked each time it is read. It wraps after it is read 255 times. (The 256th read returns the same value as the 1st). data fetchers ------------- Internal to the DPC is a 2K 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. 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: 0ABCDEFG (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 105C-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 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 0h 001 4h 010 5h 011 9h 100 6h 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. HMOVE adjustment stuff: ----------------------- I have not done much research on how this works, and P2 doesn't use it. It appears to let you draw curved lines using a missile or two, probably to make swinging vines. The patent explains how it works, but it just appears to be an adder that adds the start count value to itself via a multiplexer and stuff. It appears to let you input an offset and it calculates a new hmove value. Not sure how useful this really is. AR (Starpath Supercharger) -------------------------- Aaah the good ol' Supercharger. This is the large "cartridge" that you could plug into your 2600 that had 6K of RAM which you could download games into off of cassette tape. The idea being you plug your cassette player into the cable coming off of the cartridge, and then you can download games into RAM from it. When first turned on, it says "rewind tape" and "press play". When this is done, it will start loading the game off tape and fill the screen in with blue bars from the sides to the inside while a rising tone plays. When the screen is fully blue, it then clears the screen and says "stop tape", and then the game starts. If there is an error (corrupt tape, etc) it will continously flash "rewind tape" on the screen until the audio input stops toggling. Games can re-enable the BIOS ROM and load more data off tape at any time. So, that's what it is... but how does it work? I didn't find a whole huge amount of information on how the Supercharger actually WORKS. There was some info about how to program it, but nothing on how the ASIC in the Supercharger does its thing. Well, turns out it's pretty cool and as far as I know, unique. First off, there are two registers on the Supercharger. The first is the audio input register. It's at FFF9: 7 0 --------- FFF9: 0000 000A A: Supercharger audio data. 0 = low input, 1 = high input. The audio input's just a filtered and schmitt triggered version of the audio data coming into the Supercharger. The upper 7 bits must all be 0's, or else it will not properly read the data. For testing, I fed my FPGA 2600's Supercharger with data from Bankzilla's tape emulator. This is simply a digital output that is fed into the Supercharger's audio pin (and to my FPGA board). The next register is the control register. 7 0 --------- FFF8: DDDB BBWE D: write delay (see below) B: bankswitching mode (see below) W: RAM write enable (1 = enabled, 0 = disabled) E: ROM power enable (0 = enabled, 1 = turn off ROM) To write to RAM or the control register on the Supercharger requires a somewhat round-about method. First, you read a location at F000-F0FF, then you read either FFF8 to write to the control register, or somewhere else in RAM. Sooo, to write to the control register, the following type of code is used: ;A = value we will write to the banking register TAX LDA F000,X LDA FFF8 The TAX moves the value we wish to write into F000,X (i.e. if we wish to write 16h to the control register, the location F016 is read). Next, we read FFF8 which performs the write. *any* instruction that reads FFF8 will suffice. The BIOS uses BIT FFF8 and LDX FFF8 sometimes, depending on what's convenient, and what flags or registers need to be saved. The instruction really does not matter, so long as it simply READS. Note: THERE IS NO UPPER LIMIT on the number of cycles that can elapse between reading F0xx and reading FFF8 to write to the control register. Usually, it is written to immediately after reading F0xx, but not always. The very last write to FFF8 which sets the RAM banking before launching the game after loading waits a very long time before reading FFF8 to set the banking mode. The basic method it does the final write is as such: LDA F000,X FA0B : 9A TXS ;X entered at ffh FA0C : A2 3 LDX #$3 FA0E : BC AB FD LDY $FDAB,X FA11 : 94 FA STY $FA,X ;load cmp fff8h, jmp opcode only FA13 : CA DEX FA14 : 10 F8 BPL $FA0E ;jmp target was at fe/ff FA16 : A0 0 LDY #$0 FA18 : 84 81 STY $81 FA1A : 4C FA 0 JMP $FA ;run the code we loaded in RAM 00FA : CD F8 FF CMP $FFF8 ;sets up control register for the game 00FD : 4C xx xx JMP xxxx ;we now jump to the start of our code FDAB : CD F8 FF 4C ;this is the code loaded into RAM The control register contains a few different things. The ROM power can be turned on and off (which IMO is dumb. The ROM doesn't draw appriciable power, but they have a PNP transistor that can turn it on and off anyways. Most games turn it off when they are done loading, and then turn it back on if they need to load again from tape. I guess they were worried it was going to vampire too much current from the system, so running it only as long as necessary was their solution. The next bit is the write enable. If set, the RAM can be written to. If clear, it is read-only. This is useful for loading 2K and 4K games into the Supercharger to play them. Not much use otherwise. The Supercharger will never attempt to write to ROM, and it will not write to RAM if this bit is clear. Banking mode. The Supercharger allows for 8 different memory maps, depending on how these three bits are set. The following are supported: BBB 1000-17FF, 1800-1FFF --- --------------------- 000 - RAM bank 3 ROM 001 - RAM bank 1 ROM 010 - RAM bank 3 RAM bank 1 011 - RAM bank 1 RAM bank 3 100 - RAM bank 3 ROM 101 - RAM bank 2 ROM 110 - RAM bank 3 RAM bank 2 111 - RAM bank 2 RAM bank 3 ROM can never be mapped into 1000-17FF but this makes sense, since the code is written to only run from 1800-1FFF. Also, a few combinations are not possible such as RAM bank 1, RAM bank 2, and RAM bank 2, RAM bank 1. All others are possible. The last part of the control register is the write pulse delay. I don't think anything uses this and just keeps it at 0. Heck, it may not even be implemented. So that takes care of how to read audio and how to access the control register. That leaves writing to RAM. The Supercharger watches all 13 address lines to determine when RAM should be written. It first watches for a write to F0xx, just like when writing to the control register, then it waits 5 address bus transitions, then it attempts to write data to the RAM chip by pushing the value onto the data bus, while the CPU is reading. The usual way this is done in the BIOS is like so: ;X = byte to write LDA F000,X ;tell the supercharger what to write LDA (8B),Y ;write it! If we look at bus transitions, it looks like this: Assume that the code is sitting at 1800, and 8B/8C holds the address 1100h. Also assume X = 66h c# address, data 1: 1800 BD ;LDA,X opcode 2: 1801 00 ;low byte of add 3: 1802 10 ;hi byte of add 4: 1066 ?? ;read address 1066 (this is the RAM byte, could hold anything). 5: 1803 B1 ;LDA (),y opcode 6: 1804 8B ;zeropage address of pointer 7: 008B 00 ;low byte of destination 8: 008C 11 ;hi byte of destination 9: 1100 66 ;Supercharger writes data now to RAM by forcing value on bus As we can see, cycle 9 is exactly 5 cycles after cycle 4, where we read from F0xx. The supercharger will only attempt to write if writes are enabled, and the area we're attempting to write to is set up for RAM. It will not attempt to write to ROM, or outside the range of 1000-1FFF. This method of operation has an interesting side effect. Because the Supercharger forces a value onto the bus while the CPU is reading, the CPU will actually READ this value! This makes sense- the CPU doesn't know if it's reading RAM, ROM, or the Supercharger's write value. In fact, this is REQUIRED for it to even work. It does some pretty extensive RAM tests on powerup, and if it cannot write to RAM it will flash the screen yellowish/black as it attempts to test RAM over and over. If RAM fails to test properly, it will reset and start again. The RAM testing code looks like this: FF58 : A9 F1 LDA #$F1 FF5A : 85 8C STA $8C FF5C : A0 0 LDY #$0 ;start address = F100 FF5E : 84 8B STY $8B FF60 : AD 0 F0 LDA $F000 ;write 00h FF63 : B1 8B LDA ($8B),Y ;write ! FF65 : D1 8B CMP ($8B),Y ;is it 00h? FF67 : D0 14 BNE $FF7D ;no FF69 : AD FF F0 LDA $F0FF ;write ffh FF6C : B1 8B LDA ($8B),Y ;write ! FF6E : D1 8B CMP ($8B),Y ;is if ffh? FF70 : D0 B BNE $FF7D ;nope FF72 : C8 INY FF73 : D0 EB BNE $FF60 ;last byte of page? FF75 : E6 8C INC $8C FF77 : A5 8C LDA $8C FF79 : C9 F8 CMP #$F8 FF7B : D0 E3 BNE $FF60 ;last page? FF7D : 60 RTS ;return zero set if passed Basically what happens is this: It first sets up the address F100 (aka 1100) into 8B/8C, then it steps through the entire RAM area up to 17FF, and returns with Z flag set, if it passed, or NZ if it fails. The code at FF60-FF66 is very interesting: It first reads from F000 so that we will write 00 to memory. Then it writes it and immediately compares it. Remember that the CPU will READ what the Supercharger writes, so the accumulator will hold 00 after executing that LDA (),y! The same test is repeated, this time by writing FF. If it can step through all locations without erroring out, it passes the test. This code only tests one 2K bank (minus the first 256 locations, more on that in a minute). The code is then called three times, testing each bank of RAM in turn. This testing brings up an interesting note about the Supercharger's RAM. When writes are enabled to RAM, you CANNOT READ FROM 10xx WITHOUT CORRUPTING RAM. This means, you cannot read data here, and you cannot excute code here! Doing so will trigger writes, which will overwrite whatever happens to be 5 cycles later if it's in RAM! You *CAN* write to 1000-10FF, however just fine... you just cannot read it or execute from it without first disabling writes. In fact, the BIOS starts writing at 1000 when it's loading the game from tape. Since it never attempts to execute code from there or read back the data, this is legal. The BIOS' RAM testing routine must therefore skip testing the first 256 bytes of each bank. It *could* test it, but I guess they figured running the code from RAM to do it would've been too much of a hassle, concidering space was at a premium. Remember how I said the Supercharger counted *BUS TRANSITIONS* to know when to write to RAM? Well, this is very very important. You cannot just count CPU cycles. That won't work (because the Supercharger cannot see the CPU's clock line for one). Here's the other way that you can write to RAM: FFC7 : A0 8 LDY #$8 FFC9 : D9 0 F0 CMP $F000,Y FFCC : EA NOP FFCD : CD E3 F7 CMP $F7E3 This writes 8 to location 17E3. Notice the NOP to "fill up" the cycles. If we look at what the address/data bus is doing, we will see how this can work, even though the write occurs 6 cycles after the CMP, instead of 5. c# address, data 1: FFC9 D9 ;CMP,Y opcode 2: FFCA 00 ;low byte of add 3: FFCB F0 ;hi byte of add 4: F008 xx ;read RAM here, contents unimportant 5: FFCC EA ;NOP opcode 6: FFCD CD ;CMP opcode, but throw it away (NOP is 2 cycles) 7: FFCD CD ;CMP opcode 8: FFCE E3 ;low byte of add 9: FFCF F7 ;hi byte of add 10:F7E3 08 ;write to F7E3 Yes, it does indeed take 6 cycles to get to the write... however, look at the address bus. There's only FIVE transitions! The byte at FFCD gets read twice. This is because NOP is a 2 cycle instruction. NOP's second cycle reads the next opcode and throws it away, and does NOT increment the program counter. So, the next cycle reads it too and fetches CD for the CMP opcode. Pretty tricky stuff! Note that when I say "transition", I am talking about when one or more of the 13 address lines changes. The Supercharger can only count cycles by watching these address lines. That's why it only counts up 5 transitions on the above code snippet. The double access to FFCD counts as 1 transition as far as the Supercharger is concerned. That about wraps up the Supercharger's hardware. Supercharger Tape Format ------------------------ The tape format on the Supercharger is fairly straight forward. It's a form of pulse width modulation. Interestingly, the length of the pulses is not fixed, and the software on the Supercharger can adapt to a pretty wide range. According to the "tapedocs.txt" file: "0" pulse "1" pulse ------------------------------- minimum 158uS 317uS optimal* 227uS 340uS maximum 317uS 2450uS * According to the tapedocs.txt file, the filters on the Supercharger are tuned specifically for 227uS and 340uS pulse lengths. NOTE: these times are for the entire pulse, i.e. the low and high portions of it. The low and high portions would thus be half these values. i.e. if the pulse is 300uS long... low high ____________------------ \--150uS--/ \--150uS--/ Here is a generic representation of what a typical signal looks like: ____----____----____----________--------________--------____----________-------- \--0--/ \--0--/ \--0--/ \------1------/ \------1------/ \--0--/ \------1------/ The "one" pulses only have to be approximately 90uS wider than the "zero" pulses, but it's a good idea to have them at least 25% wider so the Supercharger can adapt to tape speed fluctuations during the load. A Supercharger tape load is composed of 6 separate parts: --------------------------------- 1. Preamble The preamble is a 50% duty cycle low frequency waveform that causes all the elements in the tape chain (amplifiers, the filter in the Supercharger proper, etc) to stabilize. Without this preamble, the volume level into the 'charger can change while the capacitors in the signal chain charge. A frequency of around 750Hz (666uS low, 666uS high) is pretty decent. The preamble needs to be at least 800 pulses long (i.e. around 1 second or more). --------------------------------- 2. Synchronization After the preamble, at least 256 or more bytes of 55h (0/1/0/1) bits need to be sent at the chosen bit rate. The Supercharger software uses this to synchronize its decoder software. --------------------------------- 3. Start pulse To tell the Supercharger our data is coming, a final 0 pulse is sent. After this point, the Supercharger header follows. --------------------------------- 4. Header data 0: WORD Start address 2: BYTE Control word 3: BYTE Block count 4: BYTE Header checksum 5: BYTE Multiload number 6: WORD Progress bar speed All values are little endian. Bytes are sent out, MSB first (i.e. left shift the data byte). Start address: The start address specifies where the 6502 will jump to after all the data is loaded from the tape. Control word: Specifies the value to load into the Supercharger's control register. Usually the upper 3 bits should be clear. (See above for more info on the control register in the Supercharger section). Block count: How many 256 byte blocks will be sent in this transfer. Header checksum: As the name indicates, a checksum of the header. The checksum is computed by starting out with 55h and subtracting every byte in the header (not including the sumcheck byte itself). The result is that when the Supercharger adds up ALL bytes of the header (including the sumcheck byte) it will be 55h. All carries/borrows are ignored when calculating the sumchecks. This is what the demo unit's header contains: .dw 0ffc0h ;start address of code .db 01bh ;control word: 1000-17ff= b.3, 1800-1fff= b.2 ROM=off WR=on .db 001h ;# of 256 byte blocks .db 06dh ;header checksum .db 000h ;this is not a multiload .dw 0010ch ;speed for progress bars when all these bytes (c0, ff, 1b, 01, 6d, 00, 0c, 01) are added together, the result is 255h... dropping carries (AND with ffh) results in 55h... meaning that the sumcheck passes and the data was properly received. Multiload number: If the game has no multiload, this byte is simply kept at 0. If this game IS a multiload game, then the sequence number is stored here. This lets Supercharger BIOS know if the proper game load is being received or not. The first load of a multiload game is always 0. When the first load wishes to load the second (or subsequent) part(s), the current load tells the Supercharger BIOS which load it is looking for and runs the BIOS' receive routine again. If the data received is not the proper load #, it will do a "false load" and load the data, but then throw the results away and wait for the next load to come around. This is repeated until the correct load number is reached and loaded. On Escape from the Mindmaster this can be seen if you lose all your lives in the first load. It will instruct you to press "play" on your tape player, and will load part 2. But the ending is not in part 2, so it throws the data away after loading it and proceeds to load part 3, which is thrown away, and then the final part is loaded. To prevent someone from getting cute, the multiload sequence numbers were never recycled. They were allocated like so: Escape from the mindmaster uses 1, 2, and 3 Dragonstomper uses 4 and 5 Survival Island uses 6 and 7 Progress bar speed: The value here specifies how fast the progress bars.. well.. progress as the game is loaded. If the value is wrong the bars will proceed to the middle before the game is fully loaded. The value is calculated by taking the block count and multiplying it by approximately 22.8 i.e. for a game that's 6K in size (each block being 256 bytes) is 24 blocks * 22.8, which is 547 (223h). This works for most bank sizes, however when only 1 bank is loaded, the value has to be 10ch for some reason I have not researched. --------------------------------- 5. Data blocks Once the header is finished sending, the data is sent in 256 byte blocks. Each block has a 2 byte header, followed by the 256 bytes of data. Block header: 0: BYTE Block location 1: BYTE Checksum Block location: This is the block location in RAM where the data will be loaded. Blocks are specified in the following format: 7 0 --------- 000B BBRR B: block number, from 0 to 7 R: RAM chip # Ram chip number is specified like so (reffing the bank numbers in the Supercharger documentation above) 00 - RAM bank 1 01 - RAM bank 2 10 - RAM bank 3 11 - invalid (would select ROM) The block number is the particular 256 byte block in the specified RAM bank. So, to load data sequentially, from RAM bank 1's first block, to RAM bank 3's last block, the following block locations would be used: RAM 1: 00, 04, 08, 0C, 10, 14, 18, 1C RAM 2: 01, 05, 09, 0D, 11, 15, 19, 1D RAM 3: 02, 06, 0A, 0E, 12, 16, 1A, 1E Checksum: The checksum for the block is calculated exactly like the header's checksum is. All 258 bytes (256 data bytes, block location byte, and sumcheck) are added together, and the result must be 55h. If the sumcheck fails, the "rewind tape" message flashes as long as audio data is coming in, then when the audio data stops coming in, it reverts to the "press play" message. --------------------------------- 6. Postamble After all the blocks of data are sent, the Supercharger will play the "Press stop" message until audio data stops coming in, and then it starts running the game. The postamble is used to detect when stop is pressed. The postamble is simply a string of "0" bits. The length of the postamble should be at least 256 bits, but it can be as long as desired. The Supercharger will not run the game until the postamble stops, however. --- Supercharger Demo Unit ---------------------- This doodad is pretty cool. First, you plug a Supercharger into your 2600. Then you plug the audio cable from it into this black box. Then the black box plugs into the joystick port. You can optionally plug a regular joystick into the other joystick port. The demo unit itself is a nondescript black box with a label on it. Inside the box is quite a bit of hardware. * 8 2764 EPROMs * 1 2732 EPROM * 1 6805E CPU * various discrete logic The basics of operation are as such: When powered up, a small bootloader is loaded into the Supercharger through its audio cable. Once this initial bootloader is loaded, the audio cable is never used again until the system is reset or power cycled. After the bootloader is sent, the rest of the data to the Supercharger's RAM is loaded through the joystick port 4 bits at a time. The net result is incredibly high loading speeds. A complete 6K of RAM data can be sent in around 250ms. Internally, there is a 6805E CPU made by Motorola. This is similar to the 68705 and other microcontrollers Motorola made, except it's the external ROM version. There is no ROM on the chip. The two IO ports are connected as such: PortA: This port is used to send data out 8 bits at a time to the 2600 through the joystick port. The byte of data is simply written here, and the 2600 picks it off when it's good and ready (there's an explaination later on of how this works exactly). PortB: 7 0 --------- AExB BBBB A: Audio output. This port pin connects to the Supercharger's audio cable through a resistor divider that cuts the audio level down to a level of a few hundred millivolts to prevent overloading the audio input. E: EPROM enable. Must be low to enable the 8 2764 EPROMs. B: EPROM bank. Selects a 2K EPROM bank from the 2764's (more about this later) PortA's data is passed through a quad 2:1 multiplexer (a 74LS157). The select line connects to bit 3 of the joystick port. When it's low, the multiplexer outputs the lower nybble of the byte on PortA. When it's high, it outputs the upper nybble. The data bits from the muxer connect like so: bit 3: this is acting as an output- it runs the select line of the 74LS157, and also connects to /IRQ on the CPU. bits 2 to 0 connect to the multiplexer's upper 3 bits. The fire button input connects to the multiplexer's lowest bit. So, to read a byte of data in through the controller port, the select line is pulled low via the RIOT register at 280h. Bit 3 must be set as an output, while bits 0-2 are inputs. bits 3,2, and 1 of the data byte are read on 280 bits 2 to 0. bit 0 of the data byte is read via 3Dh bit 7. the select line is thin pulled high via 280.3 bits 7,6, and 5 are then read from 280 bits 2 to 0 . bit 4 of the data byte is read via 3Dh bit 7. There's a very elegant piece of code that does this on the bootloader, which gets loaded into RAM: ;enters with Y = 3fh 0081 : CD 0 F0 CMP $F000 ;select proper RAM bank (note: self modifying) 0084 : CD F8 FF CMP $FFF8 0087 : A9 7F LDA #$7F 0089 : C5 3D CMP $3D ;puts fire button data into carry and invert it 008B : 4D 80 2 EOR $280 ;get data, and invert it (data is sent inverted) 008E : 8D 80 2 STA $280 ;write inverted data back (toggles bit 3 to demo unit) 0091 : 2A ROL 0092 : A ASL 0093 : A ASL 0094 : A ASL ;put lower 3 bits into bits 6 to 4, carry into 3 0095 : 85 B7 STA $B7 ;save for now 0097 : A9 7F LDA #$7F 0099 : C5 3D CMP $3D ;put fire button into carry and invert it 009B : 4D 80 2 EOR $280 ;get data, and invert it 009E : 8D 80 2 STA $280 ;write inverted data back 00A1 : 29 7 AND #$7 ;strip off upper bits 00A3 : 5 B7 ORA $B7 ;or it on to add into bits 2 to 0 00A5 : 2A ROL ;finish up by putting carry into 0 and shifting the mess left 00A6 : AA TAX 00A7 : DD 0 F0 CMP $F000,X ;write it to supercharger RAM 00AA : D1 B9 CMP ($B9),Y 00AC : 88 DEY 00AD : 10 D8 BPL $FF8A ;do all bytes 00AF : CD 19 F0 CMP $F019 ;bankswitch 00B2 : CD F8 FF CMP $FFF8 00B5 : 60 RTS This core routine is used to load data. It lives in the FF00-FFBF range and is loaded into RIOT RAM when data needs to be transferred. Since bit 3 of 280h is also connected to the /IRQ line on the CPU, this is used to tell the 6805 when a byte of data has been read, and it should get the next one ready to go. That's about it for the 2600 to demo unit interface. The memory map of the demo unit is extremely simple: 0000-0FFF : on-chip resources (ports, the timer, RAM) 1000-17FF : 2K bank of data from the 64K worth of 2764's 1800-1FFF : 2K of BIOS code from the 2732 EPROM. The 2K bank of data at 1000-17FF comes from the 64K of EPROMs. PortB bits 0-4 select a desired 2K bank. That's about it for the details. There are a few other amusing (to me) details of the hardware, however. When the unit starts, there's a 10 second countdown. This is there for a reason. Those 8 2764's can draw a lot of power if they were all turned on at once, so to enable the EPROMs there are 8 PNP transistors connected to a decoder, which connect to the VCC lines of each EPROM. So, when you wish to read from the EPROMs, you must actually enable the power line to it via a decoder chip (74LS42). To prevent a huge power suck from the 2600, there's a large capacitor that supplies power to the EPROMs. I can't remember how big it was, but I think it was 4700uF. This is connected through a resistor to 5V. So, that 10 second countdown at the start is so this capacitor can charge. The countdown code and bootstraps are stored in the 2K EPROM. Only one of the 8K EPROMs may be enabled at any time, and they can all be disabled to prevent them drawing any power by clearing bit 6 of portB. The desired EPROM is selected via bits 2,3,4 of portB. (bits 0 and 1 of portB connect to A11 and A12 of the 8 EPROMs.) The 74LS42 is a 1 of 10 decoder, and the inputs are connected as such: 7 0 --------- xDxC BAxx ABCD: the 4 selector inputs (A = LSB, D = MSB). x = not used. Thus, to enable an EPROM, bit 6 must be clear. When this bit is set, no outputs connected to EPROM power enable transistors will be turned on. I am not 100% sure of the speed the demo unit's CPU runs at. It was 12 years ago, but I think it was 4.1952MHz. I am using this on my demo unit FPGA emulation and it works good. That about wraps up this mysterious piece of hardware.