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 | |