| 1 | //============================================================================ |
| 2 | // |
| 3 | // SSSS tt lll lll |
| 4 | // SS SS tt ll ll |
| 5 | // SS tttttt eeee ll ll aaaa |
| 6 | // SSSS tt ee ee ll ll aa |
| 7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
| 8 | // SS SS tt ee ll ll aa aa |
| 9 | // SSSS ttt eeeee llll llll aaaaa |
| 10 | // |
| 11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
| 12 | // and the Stella Team |
| 13 | // |
| 14 | // See the file "License.txt" for information on usage and redistribution of |
| 15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
| 16 | //============================================================================ |
| 17 | |
| 18 | #ifndef CARTRIDGEDASH_HXX |
| 19 | #define CARTRIDGEDASH_HXX |
| 20 | |
| 21 | class System; |
| 22 | |
| 23 | #include "bspf.hxx" |
| 24 | #include "Cart.hxx" |
| 25 | |
| 26 | #ifdef DEBUGGER_SUPPORT |
| 27 | class CartridgeDASHWidget; |
| 28 | #include "CartDASHWidget.hxx" |
| 29 | #endif |
| 30 | |
| 31 | /** |
| 32 | Cartridge class for new tiling engine "Boulder Dash" format games with RAM. |
| 33 | Kind of a combination of 3F and 3E, with better switchability. |
| 34 | B.Watson's Cart3E was used as a template for building this implementation. |
| 35 | |
| 36 | The destination bank (0-3) is held in the top bits of the value written to |
| 37 | $3E (for RAM switching) or $3F (for ROM switching). The low 6 bits give |
| 38 | the actual bank number (0-63) corresponding to 512 byte blocks for RAM and |
| 39 | 1024 byte blocks for ROM. The maximum size is therefore 32K RAM and 64K ROM. |
| 40 | |
| 41 | D7D6 indicate the bank number (0-3) |
| 42 | D5D4D3D2D1D0 indicate the actual # (0-63) from the image/ram |
| 43 | |
| 44 | ROM: |
| 45 | |
| 46 | Note: in descriptions $F000 is equivalent to $1000 -- that is, we only deal |
| 47 | with the low 13 bits of addressing. Stella code uses $1000, I'm used to $F000 |
| 48 | So, mask with top bits clear :) when reading this document. |
| 49 | |
| 50 | In this scheme, the 4K address space is broken into four 1K ROM segments. |
| 51 | living at 0x1000, 0x1400, 0x1800, 0x1C00 (or, same thing, 0xF000... etc.), |
| 52 | and four 512 byte RAM segments, living at 0x1000, 0x1200, 0x1400, 0x1600 |
| 53 | with write-mirrors +0x800 of these. The last 1K ROM ($FC00-$FFFF) segment |
| 54 | in the 6502 address space (ie: $1C00-$1FFF) is initialised to point to the |
| 55 | FIRST 1K of the ROM image, so the reset vectors must be placed at the |
| 56 | end of the first 1K in the ROM image. Note, this is DIFFERENT to 3E which |
| 57 | switches in the UPPER bank and this bank is fixed. This allows variable sized |
| 58 | ROM without having to detect size. First bank (0) in ROM is the default fixed |
| 59 | bank mapped to $FC00. |
| 60 | |
| 61 | The system requires the reset vectors to be valid on a reset, so either the |
| 62 | hardware first switches in the first bank, or the programmer must ensure |
| 63 | that the reset vector is present in ALL ROM banks which might be switched |
| 64 | into the last bank area. Currently the latter (programmer onus) is required, |
| 65 | but it would be nice for the cartridge hardware to auto-switch on reset. |
| 66 | |
| 67 | ROM switching (write of block+bank number to $3F) D7D6 upper 2 bits of bank # |
| 68 | indicates the destination segment (0-3, corresponding to $F000, $F400, $F800, |
| 69 | $FC00), and lower 6 bits indicate the 1K bank to switch in. Can handle 64 |
| 70 | x 1K ROM banks (64K total). |
| 71 | |
| 72 | D7 D6 D5D4D3D2D1D0 |
| 73 | 0 0 x x x x x x switch a 1K ROM bank xxxxx to $F000 |
| 74 | 0 1 switch a 1K ROM bank xxxxx to $F400 |
| 75 | 1 0 switch a 1K ROM bank xxxxx to $F800 |
| 76 | 1 1 switch a 1K ROM bank xxxxx to $FC00 |
| 77 | |
| 78 | RAM switching (write of segment+bank number to $3E) with D7D6 upper 2 bits of |
| 79 | bank # indicates the destination RAM segment (0-3, corresponding to $F000, |
| 80 | $F200, $F400, $F600). Note that this allows contiguous 2K of RAM to be |
| 81 | configured by setting 4 consecutive RAM segments each 512 bytes with |
| 82 | consecutive addresses. However, as the write address of RAM is +0x800, this |
| 83 | invalidates ROM access as described below. |
| 84 | |
| 85 | can handle 64 x 512 byte RAM banks (32K total) |
| 86 | |
| 87 | D7 D6 D5D4D3D2D1D0 |
| 88 | 0 0 x x x x x x switch a 512 byte RAM bank xxxxx to $F000 with write @ $F800 |
| 89 | 0 1 switch a 512 byte RAM bank xxxxx to $F200 with write @ $FA00 |
| 90 | 1 0 switch a 512 byte RAM bank xxxxx to $F400 with write @ $FC00 |
| 91 | 1 1 switch a 512 byte RAM bank xxxxx to $F600 with write @ $FE00 |
| 92 | |
| 93 | It is possible to switch multiple RAM banks and ROM banks together |
| 94 | |
| 95 | For example, |
| 96 | F000-F1FF RAM bank A (512 byte READ) |
| 97 | F200-F3FF high 512 bytes of ROM bank previously loaded at F000 |
| 98 | F400 ROM bank 0 (1K) |
| 99 | F800 RAM bank A (512 byte WRITE) |
| 100 | FA00-FBFF high 512 bytes of ROM bank previously loaded at F400 |
| 101 | FC00 ROM bank 1 |
| 102 | |
| 103 | This example shows 512 bytes of RAM, and 2 1K ROM banks and two 512 byte ROM |
| 104 | bank halves. |
| 105 | |
| 106 | Switching RAM blocks (D7D6 of $3E) partially invalidates ROM blocks, as below... |
| 107 | |
| 108 | RAM block Invalidates ROM block |
| 109 | 0 0 (lower half), 2 (lower half) |
| 110 | 1 0 (upper half), 2 (upper half) |
| 111 | 2 1 (lower half), 3 (upper half) |
| 112 | 3 1 (upper half), 3 (lower half) |
| 113 | |
| 114 | For example, RAM block 1 uses address $F200-$F3FF and $FA00-$FBFF |
| 115 | ROM block 0 uses address $F000-$F3FF, and ROM block 2 uses address $F800-$FBFF |
| 116 | Switching in RAM block 1 makes F200-F3FF ROM inaccessible, however F000-F1FF is |
| 117 | still readable. So, care must be paid. |
| 118 | |
| 119 | This crazy RAM layout is useful as it allows contiguous RAM to be switched in, |
| 120 | up to 2K in one sequentially accessible block. This means you CAN have 2K of |
| 121 | consecutive RAM (don't forget to copy your reset vectors!) |
| 122 | |
| 123 | @author Andrew Davie |
| 124 | */ |
| 125 | |
| 126 | class CartridgeDASH: public Cartridge |
| 127 | { |
| 128 | friend class CartridgeDASHWidget; |
| 129 | |
| 130 | public: |
| 131 | /** |
| 132 | Create a new cartridge using the specified image and size |
| 133 | |
| 134 | @param image Pointer to the ROM image |
| 135 | @param size The size of the ROM image |
| 136 | @param md5 The md5sum of the ROM image |
| 137 | @param settings A reference to the various settings (read-only) |
| 138 | */ |
| 139 | CartridgeDASH(const ByteBuffer& image, size_t size, const string& md5, |
| 140 | const Settings& settings); |
| 141 | virtual ~CartridgeDASH() = default; |
| 142 | |
| 143 | public: |
| 144 | /** Reset device to its power-on state */ |
| 145 | void reset() override; |
| 146 | |
| 147 | /** |
| 148 | Install cartridge in the specified system. Invoked by the system |
| 149 | when the cartridge is attached to it. |
| 150 | |
| 151 | @param system The system the device should install itself in |
| 152 | */ |
| 153 | void install(System& system) override; |
| 154 | |
| 155 | /** |
| 156 | Patch the cartridge ROM. |
| 157 | |
| 158 | @param address The ROM address to patch |
| 159 | @param value The value to place into the address |
| 160 | @return Success or failure of the patch operation |
| 161 | */ |
| 162 | bool patch(uInt16 address, uInt8 value) override; |
| 163 | |
| 164 | /** |
| 165 | Access the internal ROM image for this cartridge. |
| 166 | |
| 167 | @param size Set to the size of the internal ROM image data |
| 168 | @return A pointer to the internal ROM image data |
| 169 | */ |
| 170 | const uInt8* getImage(size_t& size) const override; |
| 171 | |
| 172 | /** |
| 173 | Save the current state of this cart to the given Serializer. |
| 174 | |
| 175 | @param out The Serializer object to use |
| 176 | @return False on any errors, else true |
| 177 | */ |
| 178 | bool save(Serializer& out) const override; |
| 179 | |
| 180 | /** |
| 181 | Load the current state of this cart from the given Serializer. |
| 182 | |
| 183 | @param in The Serializer object to use |
| 184 | @return False on any errors, else true |
| 185 | */ |
| 186 | bool load(Serializer& in) override; |
| 187 | |
| 188 | /** |
| 189 | Get a descriptor for the device name (used in error checking). |
| 190 | |
| 191 | @return The name of the object |
| 192 | */ |
| 193 | string name() const override { return "CartridgeDASH" ; } |
| 194 | |
| 195 | #ifdef DEBUGGER_SUPPORT |
| 196 | /** |
| 197 | Get debugger widget responsible for accessing the inner workings |
| 198 | of the cart. |
| 199 | */ |
| 200 | CartDebugWidget* debugWidget(GuiObject* boss, const GUI::Font& lfont, |
| 201 | const GUI::Font& nfont, int x, int y, int w, int h) override |
| 202 | { |
| 203 | return new CartridgeDASHWidget(boss, lfont, nfont, x, y, w, h, *this); |
| 204 | } |
| 205 | #endif |
| 206 | |
| 207 | public: |
| 208 | /** |
| 209 | Get the byte at the specified address |
| 210 | |
| 211 | @return The byte at the specified address |
| 212 | */ |
| 213 | uInt8 peek(uInt16 address) override; |
| 214 | |
| 215 | /** |
| 216 | Change the byte at the specified address to the given value |
| 217 | |
| 218 | @param address The address where the value should be stored |
| 219 | @param value The value to be stored at the address |
| 220 | @return True if the poke changed the device address space, else false |
| 221 | */ |
| 222 | bool poke(uInt16 address, uInt8 value) override; |
| 223 | |
| 224 | private: |
| 225 | bool bankRAM(uInt8 bank); // switch a RAM bank |
| 226 | bool bankROM(uInt8 bank); // switch a ROM bank |
| 227 | |
| 228 | void bankRAMSlot(uInt16 bank); // switch in a 512b RAM slot (lower or upper 1/2 bank) |
| 229 | void bankROMSlot(uInt16 bank); // switch in a 512b RAM slot (read or write port) |
| 230 | |
| 231 | void initializeBankState(); // set all banks according to current bankInUse state |
| 232 | |
| 233 | // We have an array that indicates for each of the 8 512 byte areas of the address space, which ROM/RAM |
| 234 | // bank is used in that area. ROM switches 1K so occupies 2 successive entries for each switch. RAM occupies |
| 235 | // two as well, one 512 byte for read and one for write. The RAM locations are +0x800 apart, and the ROM |
| 236 | // are consecutive. This allows us to determine on a read/write exactly where the data is. |
| 237 | |
| 238 | static constexpr uInt16 BANK_UNDEFINED = 0x8000; // bank is undefined and inaccessible |
| 239 | std::array<uInt16, 8> bankInUse; // bank being used for ROM/RAM (eight 512 byte areas) |
| 240 | std::array<uInt16, 4> segmentInUse; // set by bank methods, to know which hotspot was accessed |
| 241 | |
| 242 | static constexpr uInt16 BANK_SWITCH_HOTSPOT_RAM = 0x3E; // writes to this address cause bankswitching |
| 243 | static constexpr uInt16 BANK_SWITCH_HOTSPOT_ROM = 0x3F; // writes to this address cause bankswitching |
| 244 | |
| 245 | static constexpr uInt8 BANK_BITS = 6; // # bits for bank |
| 246 | static constexpr uInt8 BIT_BANK_MASK = (1 << BANK_BITS) - 1; // mask for those bits |
| 247 | static constexpr uInt16 BITMASK_LOWERUPPER = 0x100; // flags lower or upper section of bank (1==upper) |
| 248 | static constexpr uInt16 BITMASK_ROMRAM = 0x200; // flags ROM or RAM bank switching (1==RAM) |
| 249 | |
| 250 | static constexpr uInt16 MAXIMUM_BANK_COUNT = (1 << BANK_BITS); |
| 251 | static constexpr uInt16 RAM_BANK_TO_POWER = 9; // 2^n = 512 |
| 252 | static constexpr uInt16 RAM_BANK_SIZE = (1 << RAM_BANK_TO_POWER); |
| 253 | static constexpr uInt16 BITMASK_RAM_BANK = (RAM_BANK_SIZE - 1); |
| 254 | static constexpr uInt32 RAM_TOTAL_SIZE = MAXIMUM_BANK_COUNT * RAM_BANK_SIZE; |
| 255 | |
| 256 | static constexpr uInt16 ROM_BANK_TO_POWER = 10; // 2^n = 1024 |
| 257 | static constexpr uInt16 ROM_BANK_SIZE = (1 << ROM_BANK_TO_POWER); |
| 258 | static constexpr uInt16 BITMASK_ROM_BANK = (ROM_BANK_SIZE - 1); |
| 259 | |
| 260 | static constexpr uInt16 ROM_BANK_COUNT = 64; |
| 261 | |
| 262 | static constexpr uInt16 RAM_WRITE_OFFSET = 0x800; |
| 263 | |
| 264 | ByteBuffer myImage; // Pointer to a dynamically allocated ROM image of the cartridge |
| 265 | size_t mySize; // Size of the ROM image |
| 266 | std::array<uInt8, RAM_TOTAL_SIZE> myRAM; |
| 267 | |
| 268 | private: |
| 269 | // Following constructors and assignment operators not supported |
| 270 | CartridgeDASH() = delete; |
| 271 | CartridgeDASH(const CartridgeDASH&) = delete; |
| 272 | CartridgeDASH(CartridgeDASH&&) = delete; |
| 273 | CartridgeDASH& operator=(const CartridgeDASH&) = delete; |
| 274 | CartridgeDASH& operator=(CartridgeDASH&&) = delete; |
| 275 | }; |
| 276 | |
| 277 | #endif |
| 278 | |