From b80a0ecdc903cd49966663dd6910f5ec27980a89 Mon Sep 17 00:00:00 2001 From: stephena Date: Sun, 11 Mar 2012 23:26:12 +0000 Subject: [PATCH] Updated CompuMate emulation. This bankswitching side of things seems to be working fine. Still TODO is the controller side, so you can actually input some data. git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@2408 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba --- src/emucore/CartCM.cxx | 168 ++++++++++++++++++++++++++++++++++++++--- src/emucore/CartCM.hxx | 99 ++++++++++++++++++------ 2 files changed, 233 insertions(+), 34 deletions(-) diff --git a/src/emucore/CartCM.cxx b/src/emucore/CartCM.cxx index 712f7a218..9515ccd70 100644 --- a/src/emucore/CartCM.cxx +++ b/src/emucore/CartCM.cxx @@ -21,6 +21,7 @@ #include #include "System.hxx" +#include "M6532.hxx" #include "CartCM.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -30,6 +31,15 @@ CartridgeCM::CartridgeCM(const uInt8* image, uInt32 size, const Settings& settin // Copy the ROM image into my buffer memcpy(myImage, image, BSPF_min(16384u, size)); createCodeAccessBase(16384); + + // This cart contains 2048 bytes extended RAM @ 0x1800 + // This RAM scheme is unique in that it doesn't require separate read/write ports + registerRamArea(0x1800, 2048, 0x00, 0x00); + + // On powerup, portA is all 1's, so the last bank of ROM is enabled and + // RAM is disabled + myStartBank = 3; + myRamState = 0x10; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -40,47 +50,144 @@ CartridgeCM::~CartridgeCM() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeCM::reset() { + // Initialize RAM + if(mySettings.getBool("ramrandom")) + for(uInt32 i = 0; i < 2048; ++i) + myRAM[i] = mySystem->randGenerator().next(); + else + memset(myRAM, 0, 2048); + + // Upon reset we switch to the startup bank + bank(myStartBank); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeCM::install(System& system) { + mySystem = &system; + uInt16 mask = mySystem->pageMask(); + + // Make sure the system we're being installed in has a page size that'll work + assert((0x1000 & mask) == 0); + + // Mirror all access in RIOT; by doing so we're taking responsibility + // for that address space in peek and poke below. + mySystem->m6532().install(system, *this); + + // Install pages for the startup bank + bank(myStartBank); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt8 CartridgeCM::peek(uInt16 address) { - return 0; + // NOTE: This does not handle accessing cart ROM/RAM, however, this function + // should never be called for ROM/RAM because of the way page accessing + // has been setup + // It will only ever be called for RIOT reads + return mySystem->m6532().peek(address); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeCM::poke(uInt16 address, uInt8) +bool CartridgeCM::poke(uInt16 address, uInt8 value) { - return false; + // NOTE: This could be called for RIOT writes or cart ROM writes + // In the latter case, the write is ignored + if(!(address & 0x1000)) + { + // RIOT mirroring, check bankswitch + if(address == 0x280) + { + myRamState = value; + bank(myRamState & 0x3); + if(value & 0x20) myColumn = 0; + if(value & 0x40) myColumn = (myColumn + 1) % 10; + } + mySystem->m6532().poke(address, value); + } + return myBankChanged; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeCM::bank(uInt16 bank) -{ - return false; +{ + if(bankLocked()) return false; + + // Remember what bank we're in + myCurrentBank = bank; + uInt16 offset = myCurrentBank << 12; + uInt16 shift = mySystem->pageShift(); + + // Although this scheme contains four 4K banks and one 2K bank, it's easier + // to think of things in terms of 2K slices, as follows: + // + // The lower 2K of cart address space always points to the lower 2K of the + // current ROM bank + // The upper 2K of cart address space can point to either the 2K of RAM or + // the upper 2K of the current ROM bank + + System::PageAccess access(0, 0, 0, this, System::PA_READ); + + // Lower 2K (always ROM) + for(uInt32 address = 0x1000; address < 0x1800; address += (1 << shift)) + { + access.directPeekBase = &myImage[offset + (address & 0x0FFF)]; + access.codeAccessBase = &myCodeAccessBase[offset + (address & 0x0FFF)]; + mySystem->setPageAccess(address >> shift, access); + } + + // Upper 2K (RAM or ROM) + for(uInt32 address = 0x1800; address < 0x2000; address += (1 << shift)) + { + access.type = System::PA_READWRITE; + + if(myRamState & 0x10) + { + access.directPeekBase = &myImage[offset + (address & 0x0FFF)]; + access.codeAccessBase = &myCodeAccessBase[offset + (address & 0x0FFF)]; + } + else + { + access.directPeekBase = &myRAM[address & 0x7FF]; + access.codeAccessBase = &myCodeAccessBase[offset + (address & 0x07FF)]; + } + + if((myRamState & 0x30) == 0x20) + access.directPokeBase = &myRAM[address & 0x7FF]; + else + access.directPokeBase = 0; + + mySystem->setPageAccess(address >> shift, access); + } + + return myBankChanged = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt16 CartridgeCM::bank() const { - return 0; + return myCurrentBank; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt16 CartridgeCM::bankCount() const { + // We report 4 banks (of ROM), even though RAM can overlap the upper 2K + // of cart address space at some times + // However, this RAM isn't enabled in the normal way that bankswitching + // works, so it is ignored here return 4; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeCM::patch(uInt16 address, uInt8 value) { - return false; + if((myRamState & 0x30) == 0x20) + myRAM[address & 0x7FF] = value; + else + myImage[(myCurrentBank << 12) + address] = value; + + return myBankChanged = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -93,11 +200,54 @@ const uInt8* CartridgeCM::getImage(int& size) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeCM::save(Serializer& out) const { - return false; + try + { + out.putString(name()); + out.putInt(myCurrentBank); + out.putByte(myRamState); + out.putByte(myColumn); + + // The 2048 bytes of RAM + out.putInt(2048); + for(uInt32 i = 0; i < 2048; ++i) + out.putByte((char)myRAM[i]); + + } + catch(const char* msg) + { + cerr << "ERROR: CartridgeCM::save" << endl << " " << msg << endl; + return false; + } + + return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeCM::load(Serializer& in) { - return false; + try + { + if(in.getString() != name()) + return false; + + myCurrentBank = (uInt16) in.getInt(); + myRamState = (uInt8) in.getByte(); + myColumn = (uInt8) in.getByte(); + + // The 2048 bytes of RAM + uInt32 limit = (uInt32) in.getInt(); + for(uInt32 i = 0; i < limit; ++i) + myRAM[i] = (uInt8) in.getByte(); + } + catch(const char* msg) + { + cerr << "ERROR: CartridgeCM::load" << endl << " " << msg << endl; + return false; + } + + // Remember what bank we were in + myRamState = (uInt8) in.getByte(); + bank(myCurrentBank); + + return true; } diff --git a/src/emucore/CartCM.hxx b/src/emucore/CartCM.hxx index 91558e46e..d5ee73b04 100644 --- a/src/emucore/CartCM.hxx +++ b/src/emucore/CartCM.hxx @@ -27,36 +27,76 @@ class System; /** Cartridge class used for SpectraVideo CompuMate bankswitched games. - There are 4 4K banks selectable at $1000 - $1FFFF. + + 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 2K of RAM. + + There are 4 4K banks selectable at $1000 - $1FFF, and 2K RAM at + $1800 - $1FFF (R/W 'line' is available at SWCHA D5, so there's no separate + read and write ports). Bankswitching is done though the controller ports - INPT0: D7 = CTRL key input (0 on startup / 1 = key pressed) - INPT1: D7 = always HIGH input (tested at startup) - INPT2: D7 = always HIGH input (tested at startup) - INPT3: D7 = SHIFT key input (0 on startup / 1 = key pressed) - INPT4: D7 = keyboard row 1 input (0 = key pressed) - INPT5: D7 = keyboard row 3 input (0 = key pressed) - SWCHA: D7 = tape recorder I/O ? - D6 = 1 -> increase key collumn (0 to 9) - D5 = 1 -> reset key collumn to 0 (if D4 = 0) - D5 = 0 -> enable RAM writing (if D4 = 1) - D4 = 1 -> map 2K of RAM at $1800 - $1fff - D3 = keyboard row 4 input (0 = key pressed) - D2 = keyboard row 2 input (0 = key pressed) + SWCHA: D7 = Audio input from tape player + D6 = Audio out to tape player and 4017 CLK + 1 -> increase key column (0 to 9) + D5 = 4017 RST, and RAM direction. (high = write, low = read) + 1 -> reset key column to 0 (if D4 = 0) + 0 -> enable RAM writing (if D4 = 1) + D4 = RAM enable: 1 = disable RAM, 0 = enable RAM + D3 = keyboard row 1 input (0 = key pressed) + D2 = keyboard row 1 input (0 = key pressed) D1 = bank select high bit D0 = bank select low bit - Keyboard column numbering: - column 0 = 7 U J M - column 1 = 6 Y H N - column 2 = 8 I K , - column 3 = 2 W S X - column 4 = 3 E D C - column 5 = 0 P ENTER SPACE - column 6 = 9 O L . - column 7 = 5 T G B - column 8 = 1 Q A Z - column 9 = 4 R F V + INPT0: D7 = CTRL key input (0 on startup / 1 = key pressed) + INPT1: D7 = always HIGH input (pulled high thru 20K resistor) + INPT2: D7 = always HIGH input (pulled high thru 20K resistor) + INPT3: D7 = SHIFT key input (0 on startup / 1 = key pressed) + INPT4: D7 = keyboard row 0 input (0 = key pressed) + INPT5: D7 = keyboard row 2 input (0 = key pressed) + + 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. @author Stephen Anthony & z26 team @version $Id$ @@ -172,6 +212,15 @@ class CartridgeCM : public Cartridge // The 16K ROM image of the cartridge uInt8 myImage[16384]; + + // The 2K of RAM + uInt8 myRAM[2048]; + + // RAM read/write state + uInt8 myRamState; + + // Column currently active + uInt8 myColumn; }; #endif