├── 6502
├── 6502Emulator
│ ├── CMakeLists.txt
│ └── src
│ │ └── olcNes_PPU_Backgrounds.cpp
└── 6502Lib
│ ├── CMakeLists.txt
│ └── src
│ └── public
│ ├── Bus.cpp
│ ├── Bus.h
│ ├── Cartridge.cpp
│ ├── Cartridge.h
│ ├── Mapper.cpp
│ ├── Mapper.h
│ ├── Mapper_000.cpp
│ ├── Mapper_000.h
│ ├── Mapper_001.cpp
│ ├── Mapper_001.h
│ ├── Mapper_002.cpp
│ ├── Mapper_002.h
│ ├── Mapper_003.cpp
│ ├── Mapper_003.h
│ ├── Mapper_004.cpp
│ ├── Mapper_004.h
│ ├── Mapper_066.cpp
│ ├── Mapper_066.h
│ ├── Nes2A03.cpp
│ ├── Nes2A03.h
│ ├── Nes2C02.cpp
│ ├── Nes2C02.h
│ ├── Nes6502.cpp
│ ├── Nes6502.h
│ ├── olcPGEX_Sound.h
│ └── olcPixelGameEngine.h
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .vscode
├── c_cpp_properties.json
└── settings.json
├── CMakeLists.txt
├── LICENSE
├── make_VS2019.bat
├── readme.md
├── readme6502.md
└── rom
├── 23.nes
├── Adventure Island 3 (USA).nes
├── Adventure Island Classic (Europe).nes
├── Adventure Island Part II, The (Europe).nes
├── Castlevania2.nes
├── Doraemon (J).nes
├── Double Dragon II - The Revenge (USA) (Rev A).nes
├── Dragon Power (USA).nes
├── Duck Hunt (JUE).nes
├── Gumshoe.nes
├── Mega Man 3 (USA).nes
├── Mega Man 6 (USA).nes
├── Metal Max (Japan).nes
├── SolomonsKey.nes
├── Zoda's Revenge - StarTropics II (USA).nes
├── chenlong.nes
├── contra.nes
├── dazhuankuai.nes
├── duola.nes
├── emocheng.nes
├── maxituan.nes
├── mmc1_a12.nes
├── nestest.nes
├── rexuezuqiu.nes
├── smb.nes
├── smb4.nes
├── 热血新纪录.nes
├── 热血时代剧中文版.nes
├── 热血曲棍球.nes
├── 热血格斗中文版.nes
├── 热血物语中文版.nes
├── 热血硬派.nes
├── 热血篮球中文版.nes
├── 热血足球cn.nes
├── 热血足球联盟cn.nes
├── 热血进行曲中文版.nes
└── 热血高校中文版.nes
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: 6502Emulator
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | env:
10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
11 | BUILD_TYPE: Release
12 |
13 | jobs:
14 | build:
15 | # The CMake configure and build commands are platform agnostic and should work equally
16 | # well on Windows or Mac. You can convert this to a matrix build if you need
17 | # cross-platform coverage.
18 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | with:
24 | fetch-depth: 0
25 | - name: Configure CMake
26 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
27 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
28 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
29 | - name: 安装mesa-common-dev
30 | run: sudo apt-get install mesa-common-dev &&
31 | sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev
32 | - name: 安装alssa
33 | run: sudo apt update && sudo apt-get install alsa-base alsa-utils alsa-source libasound2-dev
34 | - name: Build
35 | # Build your program with the given configuration
36 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
37 | - name: 编译make
38 | run: cd build && make -j$(nproc)
39 | - uses: actions/upload-artifact@master
40 | with:
41 | name: 导出二进制文件
42 | path: ./build/6502/6502Emulator/6502Emulator
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Linux",
5 | "includePath": [
6 | "${workspaceFolder}/6502/**"
7 | ],
8 | "defines": [],
9 | "compilerPath": "/usr/bin/gcc"
10 | }
11 | ],
12 | "version": 4
13 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "C_Cpp.clang_format_fallbackStyle": "google"
3 | }
--------------------------------------------------------------------------------
/6502/6502Emulator/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.7)
2 |
3 | project( 6502Emulator )
4 |
5 | set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DM6502_DEBUG" ) #so we can add the DEBUG preprocessor define and other flags to stay in debug mode - see https://cmake.org/Wiki/CMake_Useful_Variables#Compilers_and_Tools
6 | set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DM6502_DEBUG" )
7 | set(CMAKE_CXX_STANDARD 17)
8 |
9 | if(MSVC)
10 | add_compile_options(/MP) #Use multiple processors when building
11 | add_compile_options(/W4 /wd4201 ) #Warning level 4, all warnings are errors
12 | else()
13 | add_compile_options(-W -Wall ) #All Warnings, all warnings are errors
14 | endif()
15 |
16 | set (M6502_SOURCES
17 | "src/olcNes_PPU_Backgrounds.cpp"
18 | )
19 |
20 | source_group("src" FILES ${M6502_SOURCES})
21 |
22 | add_executable( 6502Emulator ${M6502_SOURCES} )
23 | add_dependencies( 6502Emulator M6502Lib )
24 | target_link_libraries(6502Emulator M6502Lib)
25 |
26 | if (${APPLE})
27 |
28 | #########################################################
29 | # FIND CARBON
30 | #########################################################
31 | FIND_LIBRARY(CARBON_LIBRARY Carbon)
32 | target_link_libraries(6502Emulator ${CARBON_LIBRARY})
33 |
34 | #########################################################
35 | # FIND PNG
36 | #########################################################
37 | find_package(PNG REQUIRED)
38 | include_directories(${PNG_INCLUDE_DIRS})
39 | link_directories(${PNG_LIBRARY_DIRS})
40 | add_definitions(${PNG_DEFINITIONS})
41 | if(NOT PNG_FOUND)
42 | message(ERROR " PNG not found!")
43 | endif(NOT PNG_FOUND)
44 | target_link_libraries(6502Emulator ${PNG_LIBRARIES})
45 |
46 | #########################################################
47 | # FIND GLUT
48 | #########################################################
49 | find_package(GLUT REQUIRED)
50 | include_directories(${GLUT_INCLUDE_DIRS})
51 | link_directories(${GLUT_LIBRARY_DIRS})
52 | add_definitions(${GLUT_DEFINITIONS})
53 | if(NOT GLUT_FOUND)
54 | message(ERROR " GLUT not found!")
55 | endif(NOT GLUT_FOUND)
56 | target_link_libraries(6502Emulator ${GLUT_LIBRARIES})
57 |
58 | #########################################################
59 | # FIND OPENGL
60 | #########################################################
61 | find_package(OpenGL REQUIRED)
62 | include_directories(${OpenGL_INCLUDE_DIRS})
63 | link_directories(${OpenGL_LIBRARY_DIRS})
64 | add_definitions(${OpenGL_DEFINITIONS})
65 | if(NOT OPENGL_FOUND)
66 | message(ERROR " OPENGL not found!")
67 | endif(NOT OPENGL_FOUND)
68 | target_link_libraries(6502Emulator ${OPENGL_LIBRARIES})
69 | else()
70 | target_LINK_LIBRARIES(6502Emulator -lasound -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17)
71 | endif()
72 |
73 |
--------------------------------------------------------------------------------
/6502/6502Emulator/src/olcNes_PPU_Backgrounds.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #include
20 | #include
21 | #include
22 |
23 | #include "Bus.h"
24 |
25 | #define OLC_PGE_APPLICATION
26 | #include "olcPixelGameEngine.h"
27 |
28 | #define OLC_PGEX_SOUND
29 | #include "olcPGEX_Sound.h"
30 |
31 | class Demo_olcNES : public olc::PixelGameEngine {
32 | public:
33 | Demo_olcNES() { sAppName = "olcNES Sound Demonstration"; }
34 |
35 | private:
36 | // The NES
37 | Bus nes;
38 | std::shared_ptr cart;
39 | bool bEmulationRun = true;
40 | float fResidualTime = 0.0f;
41 |
42 | uint8_t nSelectedPalette = 0x00;
43 |
44 | std::list audio[4];
45 | float fAccumulatedTime = 0.0f;
46 |
47 | private:
48 | // Support Utilities
49 | std::map mapAsm;
50 |
51 | std::string hex(uint32_t n, uint8_t d) {
52 | std::string s(d, '0');
53 | for (int i = d - 1; i >= 0; i--, n >>= 4)
54 | s[i] = "0123456789ABCDEF"[n & 0xF];
55 | return s;
56 | }
57 |
58 | void DrawRam(int x, int y, uint16_t nAddr, int nRows, int nColumns) {
59 | int nRamX = x, nRamY = y;
60 | for (int row = 0; row < nRows; row++) {
61 | std::string sOffset = "$" + hex(nAddr, 4) + ":";
62 | for (int col = 0; col < nColumns; col++) {
63 | sOffset += " " + hex(nes.cpuRead(nAddr, true), 2);
64 | nAddr += 1;
65 | }
66 | DrawString(nRamX, nRamY, sOffset);
67 | nRamY += 10;
68 | }
69 | }
70 |
71 | void DrawCpu(int x, int y) {
72 | std::string status = "STATUS: ";
73 | DrawString(x, y, "STATUS:", olc::WHITE);
74 | DrawString(x + 64, y, "N",
75 | nes.cpu.status & Nes6502::N ? olc::GREEN : olc::RED);
76 | DrawString(x + 80, y, "V",
77 | nes.cpu.status & Nes6502::V ? olc::GREEN : olc::RED);
78 | DrawString(x + 96, y, "-",
79 | nes.cpu.status & Nes6502::U ? olc::GREEN : olc::RED);
80 | DrawString(x + 112, y, "B",
81 | nes.cpu.status & Nes6502::B ? olc::GREEN : olc::RED);
82 | DrawString(x + 128, y, "D",
83 | nes.cpu.status & Nes6502::D ? olc::GREEN : olc::RED);
84 | DrawString(x + 144, y, "I",
85 | nes.cpu.status & Nes6502::I ? olc::GREEN : olc::RED);
86 | DrawString(x + 160, y, "Z",
87 | nes.cpu.status & Nes6502::Z ? olc::GREEN : olc::RED);
88 | DrawString(x + 178, y, "C",
89 | nes.cpu.status & Nes6502::C ? olc::GREEN : olc::RED);
90 | DrawString(x, y + 10, "PC: $" + hex(nes.cpu.pc, 4));
91 | DrawString(
92 | x, y + 20,
93 | "A: $" + hex(nes.cpu.a, 2) + " [" + std::to_string(nes.cpu.a) + "]");
94 | DrawString(
95 | x, y + 30,
96 | "X: $" + hex(nes.cpu.x, 2) + " [" + std::to_string(nes.cpu.x) + "]");
97 | DrawString(
98 | x, y + 40,
99 | "Y: $" + hex(nes.cpu.y, 2) + " [" + std::to_string(nes.cpu.y) + "]");
100 | DrawString(x, y + 50, "Stack P: $" + hex(nes.cpu.stkp, 4));
101 | }
102 |
103 | void DrawCode(int x, int y, int nLines) {
104 | auto it_a = mapAsm.find(nes.cpu.pc);
105 | int nLineY = (nLines >> 1) * 10 + y;
106 | if (it_a != mapAsm.end()) {
107 | DrawString(x, nLineY, (*it_a).second, olc::CYAN);
108 | while (nLineY < (nLines * 10) + y) {
109 | nLineY += 10;
110 | if (++it_a != mapAsm.end()) {
111 | DrawString(x, nLineY, (*it_a).second);
112 | }
113 | }
114 | }
115 |
116 | it_a = mapAsm.find(nes.cpu.pc);
117 | nLineY = (nLines >> 1) * 10 + y;
118 | if (it_a != mapAsm.end()) {
119 | while (nLineY > y) {
120 | nLineY -= 10;
121 | if (--it_a != mapAsm.end()) {
122 | DrawString(x, nLineY, (*it_a).second);
123 | }
124 | }
125 | }
126 | }
127 |
128 | void DrawAudio(int channel, int x, int y) {
129 | FillRect(x, y, 120, 120, olc::BLACK);
130 | int i = 0;
131 | for (auto s : audio[channel]) {
132 | Draw(x + i, y + (s >> (channel == 2 ? 5 : 4)), olc::YELLOW);
133 | i++;
134 | }
135 | }
136 |
137 | // This function is called by the underlying sound hardware
138 | // which runs in a different thread. It is automatically
139 | // synchronised with the sample rate of the sound card, and
140 | // expects a single "sample" to be returned, whcih ultimately
141 | // makes its way to your speakers, and then your ears, for that
142 | // lovely 8-bit bliss... but, that means we've some thread
143 | // handling to deal with, since we want both the PGE thread
144 | // and the sound system thread to interact with the emulator.
145 |
146 | static Demo_olcNES
147 | *pInstance; // Static variable that will hold a pointer to "this"
148 |
149 | static float SoundOut(int nChannel, float fGlobalTime, float fTimeStep) {
150 | if (nChannel == 0) {
151 | while (!pInstance->nes.clock()) {
152 | }
153 | return static_cast(
154 | pInstance->nes.dAudioSample); // dAudioSample是最后的播放数据
155 | } else
156 | return 0.0f;
157 | }
158 |
159 | bool OnUserCreate() override {
160 | // Load the cartridge
161 | cart = std::make_shared("./rom/smb.nes");
162 |
163 | if (!cart->ImageValid()) return false;
164 |
165 | // Insert into NES
166 | nes.insertCartridge(cart);
167 |
168 | // Extract dissassembly
169 | // mapAsm = nes.cpu.disassemble(0x0000, 0xFFFF);
170 |
171 | for (int i = 0; i < 4; i++) {
172 | for (int j = 0; j < 120; j++) audio[i].push_back(0);
173 | }
174 |
175 | // Reset NES
176 | nes.reset();
177 |
178 | // Initialise PGEX sound system, and give it a function to
179 | // call which returns a sound sample on demand
180 | pInstance = this;
181 | nes.SetSampleFrequency(44100);
182 | olc::SOUND::InitialiseAudio(44100, 1, 8, 512);
183 | olc::SOUND::SetUserSynthFunction(SoundOut);
184 | return true;
185 | }
186 |
187 | // We must play nicely now with the sound hardware, so unload
188 | // it when the application terminates
189 | bool OnUserDestroy() override {
190 | olc::SOUND::DestroyAudio();
191 | return true;
192 | }
193 |
194 | bool OnUserUpdate(float fElapsedTime) override {
195 | #ifdef __APPLE__
196 | { EmulatorUpdateWithoutAudio(fElapsedTime); }
197 | #else
198 | { EmulatorUpdateWithAudio(fElapsedTime); }
199 | #endif
200 | return true;
201 | }
202 |
203 | // This performs an emulation update but synced to audio, so it cant
204 | // perform stepping through code or frames. Essentially, it runs
205 | // the emulation in real time now, so only accepts "controller" input
206 | // and updates the display
207 | bool EmulatorUpdateWithAudio(float fElapsedTime) {
208 | // Sample audio channel output roughly once per frame
209 | fAccumulatedTime += fElapsedTime;
210 | if (fAccumulatedTime >= 1.0f / 60.0f) {
211 | fAccumulatedTime -= (1.0f / 60.0f);
212 | audio[0].pop_front();
213 | audio[0].push_back(nes.apu.pulse1_visual); // 可视化音频
214 | audio[1].pop_front();
215 | audio[1].push_back(nes.apu.pulse2_visual);
216 | audio[2].pop_front();
217 | audio[2].push_back(nes.apu.noise_visual);
218 | }
219 |
220 | Clear(olc::DARK_BLUE);
221 |
222 | // Handle input for controller in port #1
223 | nes.controller[0] = 0x00;
224 | nes.controller[0] |= GetKey(olc::Key::K).bHeld ? 0x80 : 0x00; // A Button
225 | nes.controller[0] |= GetKey(olc::Key::J).bHeld ? 0x40 : 0x00; // B Button
226 | nes.controller[0] |= GetKey(olc::Key::Y).bHeld ? 0x20 : 0x00; // Select
227 | nes.controller[0] |= GetKey(olc::Key::T).bHeld ? 0x10 : 0x00; // Start
228 | nes.controller[0] |= GetKey(olc::Key::W).bHeld ? 0x08 : 0x00;
229 | nes.controller[0] |= GetKey(olc::Key::S).bHeld ? 0x04 : 0x00;
230 | nes.controller[0] |= GetKey(olc::Key::A).bHeld ? 0x02 : 0x00;
231 | nes.controller[0] |= GetKey(olc::Key::D).bHeld ? 0x01 : 0x00;
232 |
233 | if (GetKey(olc::Key::BACK).bPressed) nes.reset();
234 | if (GetKey(olc::Key::P).bPressed) (++nSelectedPalette) &= 0x07;
235 |
236 | // 存储游戏状态
237 | if (GetKey(olc::Key::F1).bPressed) {
238 | std::ofstream ofs;
239 | ofs.open("./save_0.dat", std::ofstream::binary);
240 | ofs.write(reinterpret_cast(&nes), sizeof(nes));
241 | ofs.close();
242 | }
243 |
244 | // 还原游戏状态
245 | if (GetKey(olc::Key::F2).bPressed) {
246 | std::ifstream ifs;
247 | ifs.open("./save_0.dat", std::ofstream::binary);
248 | ifs.read(reinterpret_cast(&nes), sizeof(nes));
249 | ifs.close();
250 | }
251 |
252 | DrawCpu(516, 2);
253 | // DrawCode(516, 72, 26);
254 |
255 | // Draw OAM Contents (first 26 out of 64)
256 | // ======================================
257 | for (int i = 0; i < 26; i++) {
258 | std::string s = hex(i, 2) + ": (" +
259 | std::to_string(nes.ppu.pOAM[i * 4 + 3]) + ", " +
260 | std::to_string(nes.ppu.pOAM[i * 4 + 0]) + ") " +
261 | "ID: " + hex(nes.ppu.pOAM[i * 4 + 1], 2) +
262 | +" AT: " + hex(nes.ppu.pOAM[i * 4 + 2], 2);
263 | DrawString(516, 72 + i * 10, s);
264 | // DrawSprite(516 + 231, 72 + i * 10, &nes.ppu.GetSpriteTitle(516 +
265 | // 231, 72 + i * 10, nes.ppu.pOAM[i * 4 + 1], nes.ppu.pOAM[i * 4 +
266 | // 2], nSelectedPalette, i));
267 | }
268 |
269 | // Draw AUDIO Channels
270 | // DrawAudio(0, 520, 72);
271 | // DrawAudio(1, 644, 72);
272 | // DrawAudio(2, 520, 196);
273 | // DrawAudio(3, 644, 196);
274 |
275 | // Draw Palettes & Pattern Tables
276 | // ==============================================
277 | const int nSwatchSize = 6;
278 | for (int p = 0; p < 8; p++) // For each palette
279 | for (int s = 0; s < 4; s++) // For each index
280 | FillRect(516 + p * (nSwatchSize * 5) + s * nSwatchSize, 340,
281 | nSwatchSize, nSwatchSize,
282 | nes.ppu.GetColourFromPaletteRam(p, s));
283 |
284 | // Draw selection reticule around selected palette
285 | DrawRect(516 + nSelectedPalette * (nSwatchSize * 5) - 1, 339,
286 | (nSwatchSize * 4), nSwatchSize, olc::WHITE);
287 |
288 | // Generate Pattern Tables
289 | DrawSprite(516, 348, &nes.ppu.GetPatternTable(0, nSelectedPalette));
290 | DrawSprite(648, 348, &nes.ppu.GetPatternTable(1, nSelectedPalette));
291 |
292 | // Draw rendered output
293 | // ========================================================
294 | DrawSprite(0, 0, &nes.ppu.GetScreen(), 2);
295 | return true;
296 | }
297 |
298 | // This performs emulation with no audio synchronisation, so it is just
299 | // as before, in all the previous videos
300 | bool EmulatorUpdateWithoutAudio(float fElapsedTime) {
301 | Clear(olc::DARK_BLUE);
302 |
303 | // Handle input for controller in port #1
304 | nes.controller[0] = 0x00;
305 | nes.controller[0] |= GetKey(olc::Key::K).bHeld ? 0x80 : 0x00; // A Button
306 | nes.controller[0] |= GetKey(olc::Key::J).bHeld ? 0x40 : 0x00; // B Button
307 | nes.controller[0] |= GetKey(olc::Key::Y).bHeld ? 0x20 : 0x00; // Select
308 | nes.controller[0] |= GetKey(olc::Key::T).bHeld ? 0x10 : 0x00; // Start
309 | nes.controller[0] |= GetKey(olc::Key::W).bHeld ? 0x08 : 0x00;
310 | nes.controller[0] |= GetKey(olc::Key::S).bHeld ? 0x04 : 0x00;
311 | nes.controller[0] |= GetKey(olc::Key::A).bHeld ? 0x02 : 0x00;
312 | nes.controller[0] |= GetKey(olc::Key::D).bHeld ? 0x01 : 0x00;
313 |
314 | if (GetKey(olc::Key::SPACE).bPressed) bEmulationRun = !bEmulationRun;
315 | if (GetKey(olc::Key::BACK).bPressed) nes.reset();
316 | if (GetKey(olc::Key::P).bPressed) (++nSelectedPalette) &= 0x07;
317 |
318 | if (bEmulationRun) {
319 | if (fResidualTime > 0.0f)
320 | fResidualTime -= fElapsedTime;
321 | else {
322 | fResidualTime += (1.0f / 60.0f) - fElapsedTime;
323 | do {
324 | nes.clock();
325 | } while (!nes.ppu.frame_complete);
326 | nes.ppu.frame_complete = false;
327 | }
328 | } else {
329 | // Emulate code step-by-step
330 | if (GetKey(olc::Key::C).bPressed) {
331 | // Clock enough times to execute a whole CPU instruction
332 | do {
333 | nes.clock();
334 | } while (!nes.cpu.complete());
335 | // CPU clock runs slower than system clock, so it may be
336 | // complete for additional system clock cycles. Drain
337 | // those out
338 | do {
339 | nes.clock();
340 | } while (nes.cpu.complete());
341 | }
342 |
343 | // Emulate one whole frame
344 | if (GetKey(olc::Key::F).bPressed) {
345 | // Clock enough times to draw a single frame
346 | do {
347 | nes.clock();
348 | } while (!nes.ppu.frame_complete);
349 | // Use residual clock cycles to complete current instruction
350 | do {
351 | nes.clock();
352 | } while (!nes.cpu.complete());
353 | // Reset frame completion flag
354 | nes.ppu.frame_complete = false;
355 | }
356 | // 存储游戏状态
357 | if (GetKey(olc::Key::Z).bPressed) {
358 | std::ofstream ofs;
359 | ofs.open("./save_0.dat", std::ofstream::binary);
360 | ofs.write(reinterpret_cast(&nes), sizeof(nes));
361 | ofs.close();
362 | }
363 | }
364 |
365 | DrawCpu(516, 2);
366 | // DrawCode(516, 72, 26);
367 |
368 | // Draw OAM Contents (first 26 out of 64)
369 | // ======================================
370 | for (int i = 0; i < 26; i++) {
371 | std::string s = hex(i, 2) + ": (" +
372 | std::to_string(nes.ppu.pOAM[i * 4 + 3]) + ", " +
373 | std::to_string(nes.ppu.pOAM[i * 4 + 0]) + ") " +
374 | "ID: " + hex(nes.ppu.pOAM[i * 4 + 1], 2) +
375 | +" AT: " + hex(nes.ppu.pOAM[i * 4 + 2], 2);
376 | DrawString(516, 72 + i * 10, s);
377 | }
378 |
379 | // Draw Palettes & Pattern Tables
380 | // ==============================================
381 | const int nSwatchSize = 6;
382 | for (int p = 0; p < 8; p++) // For each palette
383 | for (int s = 0; s < 4; s++) // For each index
384 | FillRect(516 + p * (nSwatchSize * 5) + s * nSwatchSize, 340,
385 | nSwatchSize, nSwatchSize,
386 | nes.ppu.GetColourFromPaletteRam(p, s));
387 |
388 | // Draw selection reticule around selected palette
389 | DrawRect(516 + nSelectedPalette * (nSwatchSize * 5) - 1, 339,
390 | (nSwatchSize * 4), nSwatchSize, olc::WHITE);
391 |
392 | // Generate Pattern Tables
393 | DrawSprite(516, 348, &nes.ppu.GetPatternTable(0, nSelectedPalette));
394 | DrawSprite(648, 348, &nes.ppu.GetPatternTable(1, nSelectedPalette));
395 |
396 | // Draw rendered output
397 | // ========================================================
398 | DrawSprite(0, 0, &nes.ppu.GetScreen(), 2);
399 | return true;
400 | }
401 | };
402 |
403 | // Provide implementation for our static pointer
404 | Demo_olcNES *Demo_olcNES::pInstance = nullptr;
405 |
406 | int main() {
407 | Demo_olcNES demo;
408 | demo.Construct(780, 480, 2, 2);
409 | demo.Start();
410 | return 0;
411 | }
412 |
--------------------------------------------------------------------------------
/6502/6502Lib/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.7)
2 |
3 | project( M6502Lib )
4 |
5 | set(CMAKE_CXX_STANDARD 17)
6 |
7 | if(MSVC)
8 | add_compile_options(/MP) #Use multiple processors when building
9 | add_compile_options(/W4 /wd4201) #Warning level 4, all warnings are errors
10 | else()
11 | add_compile_options(-W -Wall) #All Warnings, all warnings are errors
12 | endif()
13 |
14 | set (M6502_SOURCES
15 | "src/public/Bus.h"
16 | "src/public/Bus.cpp"
17 | "src/public/Cartridge.h"
18 | "src/public/Cartridge.cpp"
19 | "src/public/Mapper.h"
20 | "src/public/Mapper.cpp"
21 | "src/public/Mapper_000.h"
22 | "src/public/Mapper_000.cpp"
23 | "src/public/Mapper_001.h"
24 | "src/public/Mapper_001.cpp"
25 | "src/public/Mapper_002.h"
26 | "src/public/Mapper_002.cpp"
27 | "src/public/Mapper_003.h"
28 | "src/public/Mapper_003.cpp"
29 | "src/public/Mapper_004.h"
30 | "src/public/Mapper_004.cpp"
31 | "src/public/Mapper_066.h"
32 | "src/public/Mapper_066.cpp"
33 | "src/public/Nes2C02.h"
34 | "src/public/Nes2C02.cpp"
35 | "src/public/Nes6502.h"
36 | "src/public/Nes6502.cpp"
37 | "src/public/olcPGEX_Sound.h"
38 | "src/public/olcPixelGameEngine.h"
39 | "src/public/Nes2A03.cpp"
40 | "src/public/Nes2A03.h"
41 | )
42 |
43 | source_group("src" FILES ${M6502_SOURCES})
44 |
45 | add_library( M6502Lib ${M6502_SOURCES} )
46 |
47 | target_include_directories ( M6502Lib PUBLIC "${PROJECT_SOURCE_DIR}/src/public")
48 | target_include_directories ( M6502Lib PRIVATE "${PROJECT_SOURCE_DIR}/src/private")
49 |
50 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Bus.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #include "Bus.h"
20 |
21 | Bus::Bus() {
22 | // 在nes最初启动时,连接cpu到bus
23 | cpu.ConnectBus(this);
24 | }
25 |
26 | Bus::~Bus() {}
27 |
28 | void Bus::SetSampleFrequency(uint32_t sample_rate) {
29 | // sample_rate是声音采样率,这个采样率决定一个声音数据的发生周期
30 | dAudioTimePerSystemSample = 1.0 / static_cast(sample_rate);
31 | dAudioTimePerNESClock =
32 | 1.0 / 5369318.0; // 1 / ppu的时钟频率 = ppu的时钟周期
33 | }
34 |
35 | void Bus::cpuWrite(uint16_t addr, uint8_t data) {
36 | if (cart->cpuWrite(addr, data)) {
37 | // cartridge进行首次判断
38 | // cartridge可以根據需要將內容映射到其他任何地址
39 | // 通过这种方式,可以扩展硬件外设,比如枪。
40 | } else if (addr >= 0x0000 && addr <= 0x1FFF) {
41 | // 系统内存地址
42 | // 只有一共8kb,但是只有2kb可以用
43 | // 其他地址是2kb的镜像,使用&0x07ff运算,只使用2kb
44 | cpuRam[addr & 0x07FF] = data;
45 | } else if (addr >= 0x2000 && addr <= 0x3FFF) {
46 | // PPU地址
47 | // ppu只有8个寄存器,在0x2000 -
48 | // 0x3fff这个范围内,都是这8个寄存器的重复镜像数据
49 | // 所以直接使用&0x0007获取正确地址即可
50 | ppu.cpuWrite(addr & 0x0007, data);
51 | } else if ((addr >= 0x4000 && addr <= 0x4013) || addr == 0x4015 ||
52 | addr == 0x4017) {
53 | // APU地址
54 | // apu的几个寄存器地址 0x4000 - 0x4013 0x4015 0x4017
55 | apu.cpuWrite(addr, data);
56 | } else if (addr == 0x4014) {
57 | // 0x4014地址是DMA传送开始标志
58 | // 当此地址被写入数据时,说明发生DMA传送
59 | // CPU周期将停止,将OAM寄存器中的数据传送到PPU中,64个精灵,直到传送结束
60 | dma_page = data;
61 | dma_addr = 0x00;
62 | dma_transfer = true;
63 | } else if (addr >= 0x4016 && addr <= 0x4017) {
64 | // 控制寄存器地址
65 | // 0x4016 - 0x4017是控制寄存器地址,cpu通过读取此地址中的数据
66 | // 获取控制命令
67 | controller_state[addr & 0x0001] = controller[addr & 0x0001];
68 | }
69 | }
70 |
71 | uint8_t Bus::cpuRead(uint16_t addr, bool bReadOnly) {
72 | uint8_t data = 0x00;
73 | if (cart->cpuRead(addr, data)) {
74 | // Cartridge 地址
75 | } else if (addr >= 0x0000 && addr <= 0x1FFF) {
76 | // 系统内存地址RAM, 每2kb都是镜像 2kb = 0x07FF
77 | data = cpuRam[addr & 0x07FF];
78 | } else if (addr >= 0x2000 && addr <= 0x3FFF) {
79 | // PPU地址 每8个字节都是镜像数据
80 | data = ppu.cpuRead(addr & 0x0007, bReadOnly);
81 | } else if (addr == 0x4015) {
82 | // APU读寄存器
83 | data = apu.cpuRead(addr);
84 | } else if (addr >= 0x4016 && addr <= 0x4017) {
85 | // 读取控制命令数据
86 | data = (controller_state[addr & 0x0001] & 0x80) > 0;
87 | controller_state[addr & 0x0001] <<= 1;
88 | }
89 |
90 | return data;
91 | }
92 |
93 | void Bus::insertCartridge(const std::shared_ptr &cartridge) {
94 | // 插入卡带,
95 | // 将卡带连接到bus和ppu
96 | this->cart = cartridge;
97 | ppu.ConnectCartridge(cartridge);
98 | }
99 |
100 | void Bus::reset() {
101 | cart->reset();
102 | cpu.reset();
103 | ppu.reset();
104 | nSystemClockCounter = 0;
105 | dma_page = 0x00;
106 | dma_addr = 0x00;
107 | dma_data = 0x00;
108 | dma_dummy = true;
109 | dma_transfer = false;
110 | }
111 |
112 | bool Bus::clock() {
113 | // 时钟, 模拟器的核心
114 | // 整个模拟器的运行"节奏"
115 | // 其中,ppu时钟通常用来决定整个模拟器速度
116 | ppu.clock();
117 |
118 | // APU时钟
119 | apu.clock();
120 |
121 | // CPU的时钟周期比PPU和APU的慢3倍,也就是说,PPU和APU每执行3次,CPU执行一次
122 | // 这是NES模拟器中规定的
123 | // 使用nSystemClockCounter来记录ppu的时钟周期. 除3取余即可得到cpu时钟
124 | if (nSystemClockCounter % 3 == 0) {
125 | // 判断是否发生DMA传送(将OAM数据传送到PPU中)
126 | if (dma_transfer) {
127 | // TODO(tiansongyu): 可能存在cpu的周期bug
128 | // 这里虽然在cpu的周期中,但实际cpu不进行clock,
129 | // 此时DMA占用cpu周期
130 | // 通常占用513或514个周期
131 | // (等待写入完成时为 1 个等待状态周期,如果在奇数 CPU 周期中为 + 1,则为
132 | // 256 个交替读 / 写周期。) dma_dummy的默认是true
133 | if (dma_dummy) {
134 | // 执行周期必须是偶数,需要等待一个cpu周期
135 | if (nSystemClockCounter % 2 == 1) {
136 | // DMA开始传送
137 | dma_dummy = false;
138 | }
139 | } else {
140 | if (nSystemClockCounter % 2 == 0) {
141 | // 偶数周期从cpu中读取数据,
142 | // dma_page存储着 0x(dma_page)xx数据
143 | // xx通常从0开始 ,0xFF结束,
144 | // 64个精灵,每个精灵4个字节,所以是256个字节,也就是0xFF大小
145 | // dma_page是地址的高位,dma_addr是地址的低位
146 | // dma_data即为从cpu中读取的数据
147 | dma_data = cpuRead(dma_page << 8 | dma_addr);
148 | } else {
149 | // 奇数周期,将数据写到PPU中
150 | ppu.pOAM[dma_addr] = dma_data;
151 | // 需要增加dma的地址偏移
152 | dma_addr++;
153 | // dma_addr是一个字节的数据
154 | // 当dma_addr大于0xFF时,会产生溢出
155 | // dma_addr = 0 说明此时DMA传送停止
156 | // 将标志寄存器更改
157 | if (dma_addr == 0x00) {
158 | dma_transfer = false;
159 | dma_dummy = true;
160 | }
161 | }
162 | }
163 | } else {
164 | // 没有发生DMA传送时,CPU时钟正常进行。
165 | cpu.clock();
166 | }
167 | }
168 |
169 | // 音频同步
170 | bool bAudioSampleReady = false;
171 | // dAudioTimePerNESClock = 1.0 / 5369318.0; //
172 | dAudioTime += dAudioTimePerNESClock;
173 | if (dAudioTime >= dAudioTimePerSystemSample) {
174 | // dAudioTimePerSystemSample = 1.0 / (double)sample_rate;
175 | // 当运行的时间大于一次采样周期的时候,进行一次发声。。。。这个速度是极快的
176 | dAudioTime -= dAudioTimePerSystemSample;
177 | // 获取需要发出的声音信息
178 | dAudioSample = apu.GetOutputSample();
179 | bAudioSampleReady = true;
180 | }
181 | if (ppu.nmi) {
182 | // PPU的中断通常发生在vertical blanking period,垂直消隐期间
183 | // 也就是scanline大于251?? 的时候,属于在屏幕的下方,
184 | // 此时cpu需要进行下一帧的名称表的准备
185 | // 不可能在绘制的时间进行准备,只能等到绘制结束,否则会产生很多问题
186 | // 唯一的机会就是在垂直消隐期间进行,也是nes设计巧妙之处
187 | ppu.nmi = false;
188 | cpu.nmi();
189 | }
190 |
191 | // Check if cartridge is requesting IRQ
192 | if (cart->GetMapper()->irqState()) {
193 | cart->GetMapper()->irqClear();
194 | cpu.irq();
195 | }
196 |
197 | nSystemClockCounter++;
198 | // 如果准备好声音数据,开始发声。。
199 | return bAudioSampleReady;
200 | }
201 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Bus.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifndef __6502_6502LIB_SRC_PUBLIC_BUS_H_
20 | #define __6502_6502LIB_SRC_PUBLIC_BUS_H_
21 |
22 | #include
23 | #include
24 | #include
25 |
26 | #include "Cartridge.h"
27 | #include "Nes2A03.h"
28 | #include "Nes2C02.h"
29 | #include "Nes6502.h"
30 |
31 | class Bus {
32 | public:
33 | Bus();
34 | ~Bus();
35 |
36 | public: // 总线
37 | // 6502 CPU
38 | Nes6502 cpu;
39 | // 2C02 PPU图形处理器。。。显卡。。。
40 | Nes2C02 ppu;
41 | // 2A03 APU 声卡。。。
42 | Nes2A03 apu;
43 | std::shared_ptr cart;
44 | // CPU中2KB的内存RAM
45 | uint8_t cpuRam[2048];
46 | // 控制器数据暂存位置
47 | uint8_t controller[2];
48 |
49 | public:
50 | // 设置声音发声频率
51 | // sample_rate是声音采样率,这个采样率决定一个声音数据的发生周期
52 | void SetSampleFrequency(uint32_t sample_rate);
53 | double dAudioSample = 0.0;
54 |
55 | private:
56 | double dAudioTime = 0.0;
57 | double dAudioGlobalTime = 0.0;
58 | double dAudioTimePerNESClock = 0.0;
59 | double dAudioTimePerSystemSample = 0.0f;
60 |
61 | public:
62 | // 总线中负责cpu的读写
63 | void cpuWrite(uint16_t addr, uint8_t data);
64 | uint8_t cpuRead(uint16_t addr, bool bReadOnly = false);
65 |
66 | private:
67 | // ppu apu 经过的总共周期
68 | uint32_t nSystemClockCounter = 0;
69 | // 控制寄存器
70 | uint8_t controller_state[2];
71 |
72 | private:
73 | // 游戏精灵的传送使用DMA机制,精灵一共有64个,每个精灵大小为4个字节
74 | // DMA传送开始时,CPU停止clock,改为DMA传送或者读取一次数据,每个周期还是只能进行读或者写一次操作
75 | // dma_page和dma_addr组成一个16位的CPU地址,
76 | // 通过DMA机制,将CPU中的数据,一个字节一个字节的送到PPU中
77 | // 由于开始时需要在CPU的偶数周期进行第一次读,所以需要513或者514个周期
78 | uint8_t dma_page = 0x00;
79 | uint8_t dma_addr = 0x00;
80 | uint8_t dma_data = 0x00;
81 |
82 | // DMA传输需要精确传送。原则上需要
83 | // 512个周期来读取和写入256字节的OAM内存,一个
84 | // 先读后写。但是,CPU需要处于“偶数”状态
85 | // 时钟周期,因此可能需要一个虚拟的空闲周期
86 | bool dma_dummy = true;
87 |
88 | // DMA传送发生标志
89 | bool dma_transfer = false;
90 |
91 | public: // 系统接口
92 | // 将卡带智能指针对象连接到内部总线
93 | void insertCartridge(const std::shared_ptr &cartridge);
94 | // 重置系统
95 | void reset();
96 | // 系统clock
97 | bool clock();
98 | };
99 | #endif // __6502_6502LIB_SRC_PUBLIC_BUS_H_
100 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Cartridge.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 |
20 | #include "Cartridge.h"
21 |
22 | #include
23 |
24 | #include
25 | Cartridge::Cartridge(const std::string &sFileName) {
26 | // iNES 格式文件头
27 | // nes文件格式wiki:https://wiki.nesdev.com/w/index.php?title=INES#Name_of_file_format
28 | // iNES0.7 iNES NES2.0
29 | struct sHeader {
30 | char name[4];
31 | uint8_t prg_rom_chunks;
32 | uint8_t chr_rom_chunks;
33 | uint8_t mapper1;
34 | uint8_t mapper2;
35 | uint8_t prg_ram_size;
36 | uint8_t tv_system1;
37 | uint8_t tv_system2;
38 | char unused[5];
39 | } header;
40 |
41 | bImageValid = false;
42 |
43 | std::ifstream ifs;
44 | ifs.open(sFileName, std::ifstream::binary);
45 | if (ifs.is_open()) {
46 | // 读iNES文件头
47 | ifs.read(reinterpret_cast(&header), sizeof(sHeader));
48 | // 如果有mapper1,则调到512字节开始读取文件内容
49 | if (header.mapper1 & 0x04) ifs.seekg(512, std::ios_base::cur);
50 |
51 | // 读取这个iNES文件所使用的mapperid号
52 | nMapperID = ((header.mapper2 >> 4) << 4) | (header.mapper1 >> 4);
53 | hw_mirror = (header.mapper1 & 0x01) ? VERTICAL : HORIZONTAL;
54 |
55 | // 这里默认是iNES文件格式,后续再添加iNES0.7 iNES2.0 // TODO
56 | uint8_t nFileType = 1;
57 | if ((header.mapper2 & 0x0C) == 0x08) nFileType = 2;
58 |
59 | if (nFileType == 0) {
60 | // TODO(tiansongyu) iNES0.7
61 | }
62 |
63 | if (nFileType == 1) {
64 | nPRGBanks = header.prg_rom_chunks;
65 | vPRGMemory.resize(nPRGBanks * 16384);
66 | ifs.read(reinterpret_cast(vPRGMemory.data()), vPRGMemory.size());
67 |
68 | nCHRBanks = header.chr_rom_chunks;
69 | if (nCHRBanks == 0) {
70 | // 根据nCHRBanks个数,分配character rom内存
71 | vCHRMemory.resize(8192);
72 | } else {
73 | // 分配character rom内存
74 | vCHRMemory.resize(nCHRBanks * 8192);
75 | }
76 | ifs.read(reinterpret_cast(vCHRMemory.data()), vCHRMemory.size());
77 | }
78 |
79 | if (nFileType == 2) {
80 | nPRGBanks = ((header.prg_ram_size & 0x07) << 8) | header.prg_rom_chunks;
81 | vPRGMemory.resize(nPRGBanks * 16384);
82 | ifs.read(reinterpret_cast(vPRGMemory.data()), vPRGMemory.size());
83 |
84 | nCHRBanks = ((header.prg_ram_size & 0x38) << 8) | header.chr_rom_chunks;
85 | vCHRMemory.resize(nCHRBanks * 8192);
86 | ifs.read((char *)vCHRMemory.data(), vCHRMemory.size());
87 | }
88 |
89 | // 选择对应的映射器
90 | switch (nMapperID) {
91 | case 0:
92 | pMapper = std::make_shared(nPRGBanks, nCHRBanks);
93 | break;
94 | case 1:
95 | pMapper = std::make_shared(nPRGBanks, nCHRBanks);
96 | break;
97 | case 2:
98 | pMapper = std::make_shared(nPRGBanks, nCHRBanks);
99 | break;
100 | case 3:
101 | pMapper = std::make_shared(nPRGBanks, nCHRBanks);
102 | break;
103 | case 4:
104 | pMapper = std::make_shared(nPRGBanks, nCHRBanks);
105 | break;
106 | case 66:
107 | pMapper = std::make_shared(nPRGBanks, nCHRBanks);
108 | break;
109 | default:
110 | std::cout << "can not find the correct mapper" << std::endl;
111 | break;
112 | }
113 | printf("MapperID is %d \n", nMapperID);
114 | bImageValid = true;
115 | ifs.close();
116 | }
117 | }
118 |
119 | Cartridge::~Cartridge() {}
120 |
121 | bool Cartridge::ImageValid() { return bImageValid; }
122 | bool Cartridge::cpuRead(uint16_t addr, uint8_t &data) {
123 | uint32_t mapped_addr = 0;
124 | // cpu读取卡带中的内容
125 |
126 | if (pMapper->cpuMapRead(addr, mapped_addr, data)) {
127 | if (mapped_addr == 0xFFFFFFFF) {
128 | // Mapper has actually set the data value, for example cartridge based RAM
129 | return true;
130 | } else {
131 | // Mapper has produced an offset into cartridge bank memory
132 | data = vPRGMemory[mapped_addr];
133 | }
134 | return true;
135 | } else
136 | return false;
137 | }
138 |
139 | bool Cartridge::cpuWrite(uint16_t addr, uint8_t data) {
140 | uint32_t mapped_addr = 0;
141 | if (pMapper->cpuMapWrite(addr, mapped_addr, data)) {
142 | if (mapped_addr == 0xFFFFFFFF) {
143 | // Mapper has actually set the data value, for example cartridge based RAM
144 | return true;
145 | } else {
146 | // Mapper has produced an offset into cartridge bank memory
147 | vPRGMemory[mapped_addr] = data;
148 | }
149 | return true;
150 | } else
151 | return false;
152 | }
153 |
154 | bool Cartridge::ppuRead(uint16_t addr, uint8_t &data) {
155 | uint32_t mapped_addr = 0;
156 | if (pMapper->ppuMapRead(addr, mapped_addr)) {
157 | data = vCHRMemory[mapped_addr];
158 | return true;
159 | } else
160 | return false;
161 | }
162 |
163 | bool Cartridge::ppuWrite(uint16_t addr, uint8_t data) {
164 | uint32_t mapped_addr = 0;
165 | if (pMapper->ppuMapWrite(addr, mapped_addr)) {
166 | vCHRMemory[mapped_addr] = data;
167 | return true;
168 | } else
169 | return false;
170 | }
171 |
172 | void Cartridge::reset() {
173 | // 卡带重置,会重置mapper
174 |
175 | if (pMapper != nullptr) pMapper->reset();
176 | }
177 |
178 | MIRROR Cartridge::Mirror() {
179 | MIRROR m = pMapper->mirror();
180 | if (m == MIRROR::HARDWARE) {
181 | // Mirror configuration was defined
182 | // in hardware via soldering
183 | return hw_mirror;
184 | } else {
185 | // Mirror configuration can be
186 | // dynamically set via mapper
187 | return m;
188 | }
189 | }
190 |
191 | std::shared_ptr Cartridge::GetMapper() { return pMapper; }
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Cartridge.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifndef _6502_6502LIB_SRC_PUBLIC_CARTRIDGE_H_
20 | #define _6502_6502LIB_SRC_PUBLIC_CARTRIDGE_H_
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | #include "Mapper_000.h"
29 | // 待添加不同种类的mapper
30 | #include "Mapper_001.h"
31 | #include "Mapper_002.h"
32 | #include "Mapper_003.h"
33 | #include "Mapper_004.h"
34 | #include "Mapper_066.h"
35 |
36 | class Cartridge {
37 | public:
38 | explicit Cartridge(const std::string &sFileName);
39 | ~Cartridge();
40 |
41 | public:
42 | bool ImageValid();
43 | std::shared_ptr GetMapper();
44 |
45 | private:
46 | bool bImageValid = false;
47 | MIRROR hw_mirror = HORIZONTAL;
48 |
49 | uint8_t nMapperID = 0;
50 | uint8_t nPRGBanks = 0;
51 | uint8_t nCHRBanks = 0;
52 |
53 | std::vector vPRGMemory;
54 | std::vector vCHRMemory;
55 |
56 | std::shared_ptr pMapper;
57 |
58 | public:
59 | // 与总线通信
60 | bool cpuRead(uint16_t addr, uint8_t &data);
61 | bool cpuWrite(uint16_t addr, uint8_t data);
62 |
63 | // 与PPU通信总线
64 | bool ppuRead(uint16_t addr, uint8_t &data);
65 | bool ppuWrite(uint16_t addr, uint8_t data);
66 |
67 | // 重置卡带
68 | void reset();
69 |
70 | MIRROR Mirror();
71 | };
72 | #endif // _6502_6502LIB_SRC_PUBLIC_CARTRIDGE_H_
73 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper.cpp:
--------------------------------------------------------------------------------
1 | // Copyright [2020-2022]
2 |
3 | #include "Mapper.h"
4 |
5 | Mapper::Mapper(uint8_t prgBanks, uint8_t chrBanks) {
6 | nPRGBanks = prgBanks;
7 | nCHRBanks = chrBanks;
8 |
9 | // reset();
10 | }
11 |
12 | Mapper::~Mapper() {}
13 |
14 | MIRROR Mapper::mirror() { return MIRROR::HARDWARE; }
15 |
16 | bool Mapper::irqState() { return false; }
17 |
18 | void Mapper::irqClear() {}
19 |
20 | void Mapper::scanline() {}
21 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper.h:
--------------------------------------------------------------------------------
1 | // Copyright [2020-2022]
2 |
3 | #pragma once
4 | #include
5 |
6 | enum MIRROR {
7 | HARDWARE,
8 | HORIZONTAL,
9 | VERTICAL,
10 | ONESCREEN_LO,
11 | ONESCREEN_HI,
12 | };
13 |
14 | class Mapper {
15 | public:
16 | Mapper(uint8_t prgBanks, uint8_t chrBanks);
17 | ~Mapper();
18 |
19 | public:
20 | // Transform CPU bus address into PRG ROM offset
21 | virtual bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr,
22 | uint8_t &data) = 0;
23 | virtual bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
24 | uint8_t data = 0) = 0;
25 | // Transform PPU bus address into CHR ROM offset
26 | virtual bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) = 0;
27 | virtual bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) = 0;
28 |
29 | virtual void reset() = 0;
30 |
31 | // Get Mirror mode if mapper is in control
32 | virtual MIRROR mirror();
33 |
34 | // IRQ Interface
35 | virtual bool irqState();
36 | virtual void irqClear();
37 |
38 | // Scanline Counting
39 | virtual void scanline();
40 |
41 | protected:
42 | // These are stored locally as many of the mappers require this information
43 | uint8_t nPRGBanks = 0;
44 | uint8_t nCHRBanks = 0;
45 | };
46 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_000.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifdef __GNUC__
20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真
21 | // 关闭 警告:unused parameter
22 | #pragma GCC diagnostic ignored "-Wtype-limits"
23 | #pragma GCC diagnostic ignored "-Wunused-parameter"
24 | #endif
25 |
26 | #include "Mapper_000.h"
27 |
28 | Mapper_000::Mapper_000(uint8_t prgBanks, uint8_t chrBanks)
29 | : Mapper(prgBanks, chrBanks) {
30 | reset();
31 | }
32 |
33 | Mapper_000::~Mapper_000() {}
34 |
35 | void Mapper_000::reset() {}
36 |
37 | bool Mapper_000::cpuMapRead(uint16_t addr, uint32_t &mapped_addr,
38 | uint8_t &data) {
39 | // if PRGROM is 16KB
40 | // CPU Address Bus PRG ROM
41 | // 0x8000 -> 0xBFFF: Map 0x0000 -> 0x3FFF
42 | // 0xC000 -> 0xFFFF: Mirror 0x0000 -> 0x3FFF
43 | // if PRGROM is 32KB
44 | // CPU Address Bus PRG ROM
45 | // 0x8000 -> 0xFFFF: Map 0x0000 -> 0x7FFF
46 |
47 | // PRGROM分为16KB和32KB两种情况
48 | // 如果16KB,则其实只有0x3FFF使用到,从0x4000 -> 0x7FFF等同于0x0000 -> 0x3FFF
49 | if (addr >= 0x8000 && addr <= 0xFFFF) {
50 | mapped_addr = addr & (nPRGBanks > 1 ? 0x7FFF : 0x3FFF);
51 | return true;
52 | }
53 |
54 | return false;
55 | }
56 |
57 | bool Mapper_000::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
58 | uint8_t data) {
59 | // data没有使用,map中没有寄存器,cpu不会向map写入数据
60 | if (addr >= 0x8000 && addr <= 0xFFFF) {
61 | mapped_addr = addr & (nPRGBanks > 1 ? 0x7FFF : 0x3FFF);
62 | return true;
63 | }
64 |
65 | return false;
66 | }
67 |
68 | bool Mapper_000::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) {
69 | // There is no mapping required for PPU
70 | // PPU Address Bus CHR ROM
71 | // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF
72 | if (addr >= 0x0000 && addr <= 0x1FFF) {
73 | mapped_addr = addr;
74 | return true;
75 | }
76 |
77 | return false;
78 | }
79 |
80 | bool Mapper_000::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) {
81 | if (addr >= 0x0000 && addr <= 0x1FFF) {
82 | if (nCHRBanks == 0) {
83 | // Treat as RAM
84 | mapped_addr = addr;
85 | return true;
86 | }
87 | }
88 |
89 | return false;
90 | }
91 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_000.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifndef _6502_6502LIB_SRC_PUBLIC_MAPPER_000_H_
20 | #define _6502_6502LIB_SRC_PUBLIC_MAPPER_000_H_
21 | #include "Mapper.h"
22 |
23 | class Mapper_000 : public Mapper {
24 | public:
25 | Mapper_000(uint8_t prgBanks, uint8_t chrBanks);
26 | ~Mapper_000();
27 |
28 | public:
29 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override;
30 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
31 | uint8_t data = 0) override;
32 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override;
33 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override;
34 | void reset() override;
35 |
36 | // No local equipment required
37 | };
38 | #endif // _6502_6502LIB_SRC_PUBLIC_MAPPER_000_H_
39 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_001.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifdef __GNUC__
20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真
21 | // 关闭 警告:unused parameter
22 |
23 | #pragma GCC diagnostic ignored "-Wtype-limits"
24 | #pragma GCC diagnostic ignored "-Wunused-parameter"
25 | #endif
26 | #include "Mapper_001.h"
27 |
28 | Mapper_001::Mapper_001(uint8_t prgBanks, uint8_t chrBanks)
29 | : Mapper(prgBanks, chrBanks) {
30 | vRAMStatic.resize(32 * 1024);
31 | }
32 |
33 | Mapper_001::~Mapper_001() {}
34 |
35 | bool Mapper_001::cpuMapRead(uint16_t addr, uint32_t &mapped_addr,
36 | uint8_t &data) {
37 | if (addr >= 0x6000 && addr <= 0x7FFF) {
38 | // 0x6000 -> 0x7FFF 是mapper中的ram
39 | // Read is from static ram on cartridge
40 | mapped_addr = 0xFFFFFFFF;
41 |
42 | // Read data from RAM
43 | data = vRAMStatic[addr & 0x1FFF];
44 |
45 | // Signal mapper has handled request
46 | return true;
47 | }
48 |
49 | if (addr >= 0x8000) {
50 | if (nControlRegister & 0b01000) {
51 | // 16K Mode
52 | if (addr >= 0x8000 && addr <= 0xBFFF) {
53 | mapped_addr = nPRGBankSelect16Lo * 0x4000 + (addr & 0x3FFF);
54 | return true;
55 | }
56 |
57 | if (addr >= 0xC000 && addr <= 0xFFFF) {
58 | mapped_addr = nPRGBankSelect16Hi * 0x4000 + (addr & 0x3FFF);
59 | return true;
60 | }
61 | } else {
62 | // 32K Mode
63 | mapped_addr = nPRGBankSelect32 * 0x8000 + (addr & 0x7FFF);
64 | return true;
65 | }
66 | }
67 |
68 | return false;
69 | }
70 |
71 | bool Mapper_001::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
72 | uint8_t data) {
73 | if (addr >= 0x6000 && addr <= 0x7FFF) {
74 | // Write is to static ram on cartridge
75 | mapped_addr = 0xFFFFFFFF;
76 |
77 | // Write data to RAM
78 | vRAMStatic[addr & 0x1FFF] = data;
79 |
80 | // Signal mapper has handled request
81 | return true;
82 | }
83 |
84 | if (addr >= 0x8000) {
85 | // 与几乎所有其他映射器不同,MMC1 通过串行端口进行配置以减少引脚数。
86 | // CPU $8000 - $FFFF 连接到一个公共移位寄存器。将位 7 设置($80 到
87 | // $FF)的值写入 $8000 - $FFFF 中的任何地址,
88 | // 会将移位寄存器清除到其初始状态。要更改寄存器的值,CPU 会在第 7
89 | // 位清除并在第 0 位中写入所需值的位进行五次写入。 在前四次写入时,MMC1 将位
90 | // 0 移入移位寄存器。在第五次写入时,MMC1 将第 0
91 | // 位和移位寄存器的内容复制到由地址的第 14 和 13 位选择的内部寄存器中,
92 | // 然后清除移位寄存器。只有在第五次写入时地址才重要,即使如此,地址的第 14
93 | // 位和第 13 位也很重要, 因为映射寄存器未完全解码,如PPU 寄存器。第 5
94 | // 次写入后,移位寄存器会自动清零,因此不需要写入第 7
95 | // 位的移位寄存器来重置它。
96 |
97 | // 当 CPU 在连续周期写入串行端口时,MMC1 将忽略除第一个之外的所有写入。当
98 | // 6502 执行读 - 修改 - 写(RMW) 指令时会发生这种情况,例如 DEC 和
99 | // ROR,通过写回旧值然后在下一个周期写入新值。至少Bill &Ted's Excellence
100 | // Adventure通过在包含 $FF 的 ROM 位置执行 INC 来重置 MMC1;MMC1 看到写回的
101 | // $FF 并忽略在下一个周期写入的 $00。[1] 这样做的原因是 MMC1
102 | // 具有显式逻辑来忽略另一个写周期之后的任何写周期。写入的位置无关紧要,例如,即使在写入
103 | // $7fff 之后的一个周期内写入 $8000 也会被 MMC1 忽略,实际上,6502
104 | // 处理器无法做到这一点。 if (data & 0x80) // 0b10000000
105 | if (data & 0x80) {
106 | // MSB is set, so reset serial loading
107 | // 7 位 0
108 | // ---- ----
109 | // Rxxx xxxD
110 | // | |
111 | // | +- 要移入移位寄存器的数据位,LSB 在前
112 | // +--------- 1:复位移位寄存器并用(Control OR $0C)写控制,
113 | // 将 PRG ROM 锁定在 $C000-$FFFF 到最后一个银行。
114 | nLoadRegister = 0x00;
115 | nLoadRegisterCount = 0;
116 | nControlRegister = nControlRegister | 0x0C;
117 | } else {
118 | // Load data in serially into load register
119 | // It arrives LSB first, so implant this at
120 | // bit 5. After 5 writes, the register is ready
121 |
122 | // ;
123 | // ;
124 | // Sets the switchable PRG ROM bank to the value of A.;
125 | // ;
126 | // A MMC1_SR MMC1_PB
127 | // setPRGBank:;
128 | // 000edcba 10000 Start with an empty shift register(SR).The 1 is used
129 | // sta $E000;
130 | // 000edcba->a1000 to detect when the SR has become full.lsr a;
131 | // > 0000edcb a1000
132 | // sta $E000;
133 | // 0000edcb->ba100
134 | // lsr a;
135 | // > 00000edc ba100
136 | // sta $E000;
137 | // 00000edc->cba10
138 | // lsr a;
139 | // > 000000ed cba10
140 | // sta $E000;
141 | // 000000ed->dcba1 Once a 1 is shifted into the last position, the SR is
142 | // full.lsr a; > 0000000e dcba1 sta $E000; 0000000e dcba1->edcba
143 | // A write with the SR full copies D0 and the SR to a bank register;
144 | // 10000($E000 - $FFFF means PRG bank number) and then clears the SR.rts
145 |
146 | nLoadRegister >>= 1;
147 | nLoadRegister |= (data & 0x01) << 4;
148 | nLoadRegisterCount++;
149 |
150 | if (nLoadRegisterCount == 5) {
151 | // Get Mapper Target Register, by examining
152 | // bits 13 & 14 of the address
153 | uint8_t nTargetRegister = (addr >> 13) & 0x03;
154 | // 0x8000 - 0x9FFF
155 | if (nTargetRegister == 0) {
156 | // Set Control Register
157 | nControlRegister = nLoadRegister & 0x1F;
158 |
159 | switch (nControlRegister & 0x03) {
160 | case 0:
161 | mirrormode = ONESCREEN_LO;
162 | break;
163 | case 1:
164 | mirrormode = ONESCREEN_HI;
165 | break;
166 | case 2:
167 | mirrormode = VERTICAL;
168 | break;
169 | case 3:
170 | mirrormode = HORIZONTAL;
171 | break;
172 | }
173 | } else if (nTargetRegister == 1) { // 0xA000 - 0xBFFF
174 | // Set CHR Bank Lo
175 | if (nControlRegister & 0b10000) {
176 | // 4K CHR Bank at PPU 0x0000
177 | nCHRBankSelect4Lo = nLoadRegister & 0x1F;
178 | } else {
179 | // 8K CHR Bank at PPU 0x0000
180 | nCHRBankSelect8 = nLoadRegister & 0x1E;
181 | }
182 | } else if (nTargetRegister == 2) { // 0xC000 - 0xDFFF
183 | // Set CHR Bank Hi
184 | if (nControlRegister & 0b10000) {
185 | // 4K CHR Bank at PPU 0x1000
186 | nCHRBankSelect4Hi = nLoadRegister & 0x1F;
187 | }
188 | } else if (nTargetRegister == 3) { // 0xE000 - 0xFFFF
189 | // Configure PRG Banks
190 | uint8_t nPRGMode = (nControlRegister >> 2) & 0x03;
191 |
192 | if (nPRGMode == 0 || nPRGMode == 1) {
193 | // Set 32K PRG Bank at CPU 0x8000
194 | nPRGBankSelect32 = (nLoadRegister & 0x0E) >> 1;
195 | } else if (nPRGMode == 2) {
196 | // Fix 16KB PRG Bank at CPU 0x8000 to First Bank
197 | nPRGBankSelect16Lo = 0;
198 | // Set 16KB PRG Bank at CPU 0xC000
199 | nPRGBankSelect16Hi = nLoadRegister & 0x0F; // ?????
200 | } else if (nPRGMode == 3) {
201 | // Set 16KB PRG Bank at CPU 0x8000
202 | nPRGBankSelect16Lo = nLoadRegister & 0x0F; // ?????
203 | // Fix 16KB PRG Bank at CPU 0xC000 to Last Bank
204 | nPRGBankSelect16Hi = nPRGBanks - 1;
205 | }
206 | }
207 |
208 | // 5 bits were written, and decoded, so
209 | // reset load register
210 | nLoadRegister = 0x00;
211 | nLoadRegisterCount = 0;
212 | }
213 | }
214 | }
215 |
216 | // Mapper has handled write, but do not update ROMs
217 | return false;
218 | }
219 |
220 | bool Mapper_001::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) {
221 | if (addr < 0x2000) {
222 | if (nCHRBanks == 0) {
223 | mapped_addr = addr;
224 | return true;
225 | } else {
226 | if (nControlRegister & 0b10000) {
227 | // 4K CHR Bank Mode
228 | if (addr >= 0x0000 && addr <= 0x0FFF) {
229 | mapped_addr = nCHRBankSelect4Lo * 0x1000 + (addr & 0x0FFF);
230 | return true;
231 | }
232 |
233 | if (addr >= 0x1000 && addr <= 0x1FFF) {
234 | mapped_addr = nCHRBankSelect4Hi * 0x1000 + (addr & 0x0FFF);
235 | return true;
236 | }
237 | } else {
238 | // 8K CHR Bank Mode
239 | mapped_addr = nCHRBankSelect8 * 0x2000 + (addr & 0x1FFF);
240 | return true;
241 | }
242 | }
243 | }
244 |
245 | return false;
246 | }
247 |
248 | bool Mapper_001::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) {
249 | if (addr < 0x2000) {
250 | if (nCHRBanks == 0) {
251 | mapped_addr = addr;
252 | return true;
253 | }
254 |
255 | return true;
256 | } else
257 | return false;
258 | }
259 |
260 | void Mapper_001::reset() {
261 | nControlRegister = 0x1C;
262 | nLoadRegister = 0x00;
263 | nLoadRegisterCount = 0x00;
264 |
265 | nCHRBankSelect4Lo = 0;
266 | nCHRBankSelect4Hi = 0;
267 | nCHRBankSelect8 = 0;
268 |
269 | nPRGBankSelect32 = 0;
270 | nPRGBankSelect16Lo = 0;
271 | nPRGBankSelect16Hi = nPRGBanks - 1;
272 | }
273 |
274 | MIRROR Mapper_001::mirror() { return mirrormode; }
275 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_001.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifndef _6502_6502LIB_SRC_PUBLIC_MAPPER_001_H_
20 | #define _6502_6502LIB_SRC_PUBLIC_MAPPER_001_H_
21 | #include
22 |
23 | #include "Mapper.h"
24 |
25 | class Mapper_001 : public Mapper {
26 | public:
27 | Mapper_001(uint8_t prgBanks, uint8_t chrBanks);
28 | ~Mapper_001();
29 |
30 | public:
31 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override;
32 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
33 | uint8_t data = 0) override;
34 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override;
35 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override;
36 | void reset() override;
37 |
38 | MIRROR mirror();
39 |
40 | private:
41 | uint8_t nCHRBankSelect4Lo = 0x00;
42 | uint8_t nCHRBankSelect4Hi = 0x00;
43 | uint8_t nCHRBankSelect8 = 0x00;
44 |
45 | uint8_t nPRGBankSelect16Lo = 0x00;
46 | uint8_t nPRGBankSelect16Hi = 0x00;
47 | uint8_t nPRGBankSelect32 = 0x00;
48 |
49 | uint8_t nLoadRegister = 0x00;
50 | uint8_t nLoadRegisterCount = 0x00;
51 | uint8_t nControlRegister = 0x00;
52 |
53 | // No local equipment required
54 |
55 | MIRROR mirrormode = MIRROR::HORIZONTAL;
56 |
57 | std::vector vRAMStatic;
58 | };
59 | #endif // _6502_6502LIB_SRC_PUBLIC_MAPPER_001_H_
60 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_002.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifdef __GNUC__
20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真
21 | // 关闭 警告:unused parameter
22 | #pragma GCC diagnostic ignored "-Wtype-limits"
23 | #pragma GCC diagnostic ignored "-Wunused-parameter"
24 | #endif
25 | #include "Mapper_002.h"
26 |
27 | Mapper_002::Mapper_002(uint8_t prgBanks, uint8_t chrBanks)
28 | : Mapper(prgBanks, chrBanks) {
29 | reset();
30 | }
31 |
32 | Mapper_002::~Mapper_002() {}
33 |
34 | void Mapper_002::reset() {
35 | regLo = 0x00;
36 | regHi = nPRGBanks - 1;
37 | }
38 |
39 | bool Mapper_002::cpuMapRead(uint16_t addr, uint32_t &mapped_addr,
40 | uint8_t &data) {
41 | if (addr >= 0x8000 && addr <= 0xBFFF) {
42 | mapped_addr = regLo * 0x4000 + (addr & 0x3FFF);
43 | return true;
44 | } else if (addr >= 0xC000 && addr <= 0xFFFF) {
45 | mapped_addr = regHi * 0x4000 + (addr & 0x3FFF);
46 | return true;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | bool Mapper_002::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
53 | uint8_t data) {
54 | if (addr >= 0x8000 && addr <= 0xFFFF) {
55 | regLo = data & 0x0F;
56 | }
57 | return false;
58 | }
59 |
60 | bool Mapper_002::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) {
61 | // There is no mapping required for PPU
62 | // PPU Address Bus CHR ROM
63 | // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF
64 | if (addr >= 0x0000 && addr <= 0x1FFF) {
65 | mapped_addr = addr;
66 | return true;
67 | }
68 |
69 | return false;
70 | }
71 |
72 | bool Mapper_002::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) {
73 | if (addr >= 0x0000 && addr <= 0x1FFF) {
74 | if (nCHRBanks == 0) {
75 | // Treat as RAM
76 | mapped_addr = addr;
77 | return true;
78 | }
79 | }
80 |
81 | return false;
82 | }
83 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_002.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 |
20 | #pragma once
21 | #include "Mapper.h"
22 |
23 | class Mapper_002 : public Mapper {
24 | public:
25 | Mapper_002(uint8_t prgBanks, uint8_t chrBanks);
26 | ~Mapper_002();
27 |
28 | public:
29 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override;
30 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
31 | uint8_t data = 0) override;
32 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override;
33 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override;
34 | void reset() override;
35 | // No local equipment required
36 |
37 | private:
38 | uint8_t regLo = 0x00;
39 | uint8_t regHi = 0x00;
40 | };
41 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_003.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifdef __GNUC__
20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真
21 | // 关闭 警告:unused parameter
22 | #pragma GCC diagnostic ignored "-Wtype-limits"
23 | #pragma GCC diagnostic ignored "-Wunused-parameter"
24 | #endif
25 | #include "Mapper_003.h"
26 |
27 | Mapper_003::Mapper_003(uint8_t prgBanks, uint8_t chrBanks)
28 | : Mapper(prgBanks, chrBanks) {
29 | reset();
30 | }
31 |
32 | Mapper_003::~Mapper_003() {}
33 |
34 | void Mapper_003::reset() {
35 | regHi = 0x00;
36 | regLo = 0x00;
37 | }
38 |
39 | bool Mapper_003::cpuMapRead(uint16_t addr, uint32_t &mapped_addr,
40 | uint8_t &data) {
41 | // 存在ROM为16KB和32KB两种情况
42 | if (addr >= 0x8000 && addr <= 0xFFFF) {
43 | if (nPRGBanks == 1) // 16KB
44 | mapped_addr = addr & 0x3FFF;
45 | else if (nPRGBanks == 2) // 32KB
46 | mapped_addr = addr & 0x7FFF;
47 | return true;
48 | }
49 |
50 | return false;
51 | }
52 |
53 | bool Mapper_003::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
54 | uint8_t data) {
55 | // 修改寄存器regLo的值
56 | if (addr >= 0x8000 && addr <= 0xFFFF) {
57 | regLo = data & 0b00000011;
58 | }
59 | return false;
60 | }
61 |
62 | bool Mapper_003::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) {
63 | // There is no mapping required for PPU
64 | // PPU Address Bus CHR ROM
65 | // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF
66 |
67 | // mapper_3
68 | // PPU $0000-$1FFF: 8 KB switchable CHR ROM bank
69 | if (addr >= 0x0000 && addr <= 0x1FFF) {
70 | mapped_addr = regLo * 0x2000 + addr;
71 | return true;
72 | }
73 |
74 | return false;
75 | }
76 |
77 | bool Mapper_003::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) {
78 | return false;
79 | }
80 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_003.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 |
20 | #pragma once
21 | #include "Mapper.h"
22 |
23 | class Mapper_003 : public Mapper {
24 | public:
25 | Mapper_003(uint8_t prgBanks, uint8_t chrBanks);
26 | ~Mapper_003();
27 |
28 | public:
29 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override;
30 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
31 | uint8_t data = 0) override;
32 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override;
33 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override;
34 | void reset() override;
35 |
36 | private:
37 | uint8_t regHi = 0x00;
38 | uint8_t regLo = 0x00;
39 | // No local equipment required
40 | };
41 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_004.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2020-2022 tiansongyu
2 | //
3 | // This file is part of 6502Emulator.
4 | //
5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // 6502Emulator is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with 6502Emulator. If not, see .
17 | //
18 | // 6502Emulator is actively maintained and developed!
19 | #ifdef __GNUC__
20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真
21 | // 关闭 警告:unused parameter
22 | #pragma GCC diagnostic ignored "-Wtype-limits"
23 | #pragma GCC diagnostic ignored "-Wunused-parameter"
24 | #endif
25 | #include "Mapper_004.h"
26 |
27 | #include
28 |
29 | Mapper_004::Mapper_004(uint8_t prgBanks, uint8_t chrBanks)
30 | : Mapper(prgBanks, chrBanks) {
31 | vRAMStatic.resize(32 * 1024);
32 | }
33 |
34 | Mapper_004::~Mapper_004() {}
35 |
36 | bool Mapper_004::cpuMapRead(uint16_t addr, uint32_t &mapped_addr,
37 | uint8_t &data) {
38 | if (addr >= 0x6000 && addr <= 0x7FFF) {
39 | // 0x6000 -> 0x7FFF 是mapper中的ram
40 | // Read is from static ram on cartridge
41 | mapped_addr = 0xFFFFFFFF;
42 |
43 | // Read data from RAM
44 | data = vRAMStatic[addr & 0x1FFF];
45 |
46 | // Signal mapper has handled request
47 | return true;
48 | }
49 | if (addr >= 0x8000 && addr <= 0x9FFF) {
50 | mapped_addr = pPRGBank[0] + (addr & 0x1FFF);
51 | return true;
52 | }
53 |
54 | if (addr >= 0xA000 && addr <= 0xBFFF) {
55 | mapped_addr = pPRGBank[1] + (addr & 0x1FFF);
56 | return true;
57 | }
58 |
59 | if (addr >= 0xC000 && addr <= 0xDFFF) {
60 | mapped_addr = pPRGBank[2] + (addr & 0x1FFF);
61 | return true;
62 | }
63 |
64 | if (addr >= 0xE000 && addr <= 0xFFFF) {
65 | mapped_addr = pPRGBank[3] + (addr & 0x1FFF);
66 | return true;
67 | }
68 | return false;
69 | }
70 |
71 | bool Mapper_004::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
72 | uint8_t data) {
73 | if (addr >= 0x6000 && addr <= 0x7FFF) {
74 | // Write is to static ram on cartridge
75 | mapped_addr = 0xFFFFFFFF;
76 |
77 | // Write data to RAM
78 | vRAMStatic[addr & 0x1FFF] = data;
79 |
80 | // Signal mapper has handled request
81 | return true;
82 | }
83 | if (addr >= 0x8000 && addr <= 0x9FFF) {
84 | // Bank Select
85 | // 判断为偶数时
86 | if (!(addr & 0x0001)) {
87 | // nTargetRegister = data & 0x07;
88 | // bPRGBankMode = (data & 0x40);
89 | // bCHRInversion = (data & 0x80);
90 | nBankSelectRegister = data;
91 | } else {
92 | // Update target register
93 | pRegister[nBankSelectRegister & 0x07] = data;
94 |
95 | // Update Pointer Table
96 | // R0 and R1 ignore the bottom bit,
97 | // as the value written still counts banks in 1KB units but odd
98 | // numbered banks can't be selected.
99 |
100 | if (nBankSelectRegister & 0x80) {
101 | pCHRBank[0] = pRegister[2] * 0x0400;
102 | pCHRBank[1] = pRegister[3] * 0x0400;
103 | pCHRBank[2] = pRegister[4] * 0x0400;
104 | pCHRBank[3] = pRegister[5] * 0x0400;
105 | pCHRBank[4] = (pRegister[0] & 0xFE) * 0x0400;
106 | pCHRBank[5] = pRegister[0] * 0x0400 + 0x0400;
107 | pCHRBank[6] = (pRegister[1] & 0xFE) * 0x0400;
108 | pCHRBank[7] = pRegister[1] * 0x0400 + 0x0400;
109 | } else {
110 | pCHRBank[0] = (pRegister[0] & 0xFE) * 0x0400;
111 | pCHRBank[1] = pRegister[0] * 0x0400 + 0x0400;
112 | pCHRBank[2] = (pRegister[1] & 0xFE) * 0x0400;
113 | pCHRBank[3] = pRegister[1] * 0x0400 + 0x0400;
114 | pCHRBank[4] = pRegister[2] * 0x0400;
115 | pCHRBank[5] = pRegister[3] * 0x0400;
116 | pCHRBank[6] = pRegister[4] * 0x0400;
117 | pCHRBank[7] = pRegister[5] * 0x0400;
118 | }
119 | // R6 and R7 will ignore the top two bits, as the MMC3 has only 6
120 | // PRG ROM address lines. Some romhacks rely on an 8-bit extension
121 | // of R6/7 for oversized PRG-ROM, but this is deliberately not
122 | // supported by many emulators. See iNES Mapper 004 below.
123 | if (nBankSelectRegister & 0x40) {
124 | pPRGBank[2] = (pRegister[6] & 0x3F) * 0x2000;
125 | pPRGBank[0] = (nPRGBanks * 2 - 2) * 0x2000;
126 | } else {
127 | pPRGBank[0] = (pRegister[6] & 0x3F) * 0x2000;
128 | pPRGBank[2] = (nPRGBanks * 2 - 2) * 0x2000;
129 | }
130 |
131 | pPRGBank[1] = (pRegister[7] & 0x3F) * 0x2000;
132 | pPRGBank[3] = (nPRGBanks * 2 - 1) * 0x2000;
133 | }
134 |
135 | return false;
136 | }
137 |
138 | if (addr >= 0xA000 && addr <= 0xBFFF) {
139 | if (!(addr & 0x0001)) {
140 | // Mirroring
141 | // 这个寄存器有什么TM的用???????
142 | // 这不是直接赋值了吗,有个屁用
143 | if (data & 0x01)
144 | mirrormode = MIRROR::HORIZONTAL;
145 | else
146 | mirrormode = MIRROR::VERTICAL;
147 | } else {
148 | // PRG Ram Protect
149 | // TODO(tiansongyu)
150 | }
151 | return false;
152 | }
153 |
154 | if (addr >= 0xC000 && addr <= 0xDFFF) {
155 | if (!(addr & 0x0001)) {
156 | nIrqLatch = data;
157 | } else {
158 | nIrqCounter = 0x0000;
159 | }
160 | return false;
161 | }
162 |
163 | if (addr >= 0xE000 && addr <= 0xFFFF) {
164 | if (!(addr & 0x0001)) {
165 | bIRQEnable = false;
166 | bIRQActive = false;
167 | } else {
168 | bIRQEnable = true;
169 | }
170 | return false;
171 | }
172 |
173 | return false;
174 | }
175 |
176 | bool Mapper_004::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) {
177 | if (addr >= 0x0000 && addr <= 0x03FF) {
178 | mapped_addr = pCHRBank[0] + (addr & 0x03FF);
179 | return true;
180 | }
181 |
182 | if (addr >= 0x0400 && addr <= 0x07FF) {
183 | mapped_addr = pCHRBank[1] + (addr & 0x03FF);
184 | return true;
185 | }
186 |
187 | if (addr >= 0x0800 && addr <= 0x0BFF) {
188 | mapped_addr = pCHRBank[2] + (addr & 0x03FF);
189 | return true;
190 | }
191 |
192 | if (addr >= 0x0C00 && addr <= 0x0FFF) {
193 | mapped_addr = pCHRBank[3] + (addr & 0x03FF);
194 | return true;
195 | }
196 |
197 | if (addr >= 0x1000 && addr <= 0x13FF) {
198 | mapped_addr = pCHRBank[4] + (addr & 0x03FF);
199 | return true;
200 | }
201 |
202 | if (addr >= 0x1400 && addr <= 0x17FF) {
203 | mapped_addr = pCHRBank[5] + (addr & 0x03FF);
204 | return true;
205 | }
206 |
207 | if (addr >= 0x1800 && addr <= 0x1BFF) {
208 | mapped_addr = pCHRBank[6] + (addr & 0x03FF);
209 | return true;
210 | }
211 |
212 | if (addr >= 0x1C00 && addr <= 0x1FFF) {
213 | mapped_addr = pCHRBank[7] + (addr & 0x03FF);
214 | return true;
215 | }
216 |
217 | return false;
218 | }
219 |
220 | bool Mapper_004::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) {
221 | return false;
222 | }
223 |
224 | void Mapper_004::reset() {
225 | nBankSelectRegister = 0x00;
226 | nBankData = 0x00;
227 | nPrgRamProtect = 0x00;
228 | nIrqCounter = 0x00;
229 | nIrqLatch = 0x00;
230 |
231 | mirrormode = MIRROR::HORIZONTAL;
232 |
233 | for (int i = 0; i < 4; i++) pPRGBank[i] = 0;
234 | for (int i = 0; i < 8; i++) {
235 | pCHRBank[i] = 0;
236 | pRegister[i] = 0;
237 | }
238 |
239 | pPRGBank[0] = 0 * 0x2000;
240 | pPRGBank[1] = 1 * 0x2000;
241 | pPRGBank[2] = (nPRGBanks * 2 - 2) * 0x2000;
242 | pPRGBank[3] = (nPRGBanks * 2 - 1) * 0x2000;
243 | }
244 |
245 | MIRROR Mapper_004::mirror() { return mirrormode; }
246 |
247 | bool Mapper_004::irqState() { return bIRQActive; }
248 |
249 | void Mapper_004::irqClear() { bIRQActive = false; }
250 |
251 | void Mapper_004::scanline() {
252 | if (nIrqCounter == 0) {
253 | nIrqCounter = nIrqLatch;
254 | } else
255 | nIrqCounter--;
256 | if (nIrqCounter == 0 && bIRQEnable) {
257 | bIRQActive = true;
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_004.h:
--------------------------------------------------------------------------------
1 | // Copyright [2020-2022]
2 |
3 | #pragma once
4 | #include
5 |
6 | #include "Mapper.h"
7 |
8 | class Mapper_004 : public Mapper {
9 | public:
10 | Mapper_004(uint8_t prgBanks, uint8_t chrBanks);
11 | ~Mapper_004();
12 |
13 | public:
14 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override;
15 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
16 | uint8_t data = 0) override;
17 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override;
18 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override;
19 | void reset() override;
20 |
21 | MIRROR mirror();
22 |
23 | // IRQ Interface
24 | bool irqState();
25 | void irqClear();
26 |
27 | // Scanline Counting
28 | void scanline();
29 |
30 | private:
31 | uint8_t nBankSelectRegister = 0x00;
32 | uint8_t nBankData = 0x00;
33 | uint8_t nPrgRamProtect = 0x00;
34 | uint8_t nIrqCounter = 0x00;
35 | // nIrqLatch指的是发生中断时,刷新界面的行数
36 | uint8_t nIrqLatch = 0x00;
37 | uint32_t pRegister[8];
38 | uint32_t pCHRBank[8];
39 | uint32_t pPRGBank[4];
40 |
41 | bool bIRQEnable = false;
42 | bool bIRQActive = false;
43 |
44 | // No local equipment required
45 |
46 | MIRROR mirrormode = MIRROR::HORIZONTAL;
47 |
48 | std::vector vRAMStatic;
49 | };
50 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_066.cpp:
--------------------------------------------------------------------------------
1 | // Copyright [2020-2022]
2 | #ifdef __GNUC__
3 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真
4 | // 关闭 警告:unused parameter
5 | #pragma GCC diagnostic ignored "-Wtype-limits"
6 | #pragma GCC diagnostic ignored "-Wunused-parameter"
7 |
8 | #endif
9 | #include "Mapper_066.h"
10 |
11 | Mapper_066::Mapper_066(uint8_t prgBanks, uint8_t chrBanks)
12 | : Mapper(prgBanks, chrBanks) {
13 | reset();
14 | }
15 |
16 | Mapper_066::~Mapper_066() {}
17 |
18 | void Mapper_066::reset() {
19 | regHi = 0x00;
20 | regLo = 0x00;
21 | }
22 |
23 | bool Mapper_066::cpuMapRead(uint16_t addr, uint32_t &mapped_addr,
24 | uint8_t &data) {
25 | // CPU $8000-$FFFF: 32 KB switchable PRG ROM bank
26 | // PPU $0000-$1FFF: 8 KB switchable CHR ROM bank
27 |
28 | if (addr >= 0x8000 && addr <= 0xFFFF) {
29 | mapped_addr = regHi * 0x8000 + (addr & 0x7FFF);
30 | return true;
31 | }
32 |
33 | return false;
34 | }
35 |
36 | bool Mapper_066::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
37 | uint8_t data) {
38 | // 7 bit 0
39 | // ---- ----
40 | // xxPP xxCC
41 | // || ||
42 | // || ++- Select 8 KB CHR ROM bank for PPU $0000-$1FFF
43 | // ++------ Select 32 KB PRG ROM bank for CPU $8000-$FFFF
44 | if (addr >= 0x8000 && addr <= 0xFFFF) {
45 | regLo = data & 0b00000011;
46 | regHi = (data & 0b00110000) >> 4;
47 | return true;
48 | }
49 |
50 | return false;
51 | }
52 |
53 | bool Mapper_066::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) {
54 | // There is no mapping required for PPU
55 | // PPU Address Bus CHR ROM
56 | // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF
57 | if (addr >= 0x0000 && addr <= 0x1FFF) {
58 | mapped_addr = regLo * 0x2000 + addr;
59 | return true;
60 | }
61 |
62 | return false;
63 | }
64 |
65 | bool Mapper_066::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) {
66 | return false;
67 | }
68 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Mapper_066.h:
--------------------------------------------------------------------------------
1 | // Copyright [2020-2022]
2 |
3 | #pragma once
4 | #include "Mapper.h"
5 |
6 | class Mapper_066 : public Mapper {
7 | public:
8 | Mapper_066(uint8_t prgBanks, uint8_t chrBanks);
9 | ~Mapper_066();
10 |
11 | public:
12 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override;
13 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr,
14 | uint8_t data = 0) override;
15 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override;
16 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override;
17 | void reset() override;
18 |
19 | private:
20 | uint8_t regHi = 0x00;
21 | uint8_t regLo = 0x00;
22 | // No local equipment required
23 | };
24 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Nes2A03.cpp:
--------------------------------------------------------------------------------
1 | // Copyright [2020-2022]
2 |
3 | #include "Nes2A03.h"
4 |
5 | uint8_t Nes2A03::length_table[] = {
6 | 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
7 | 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30};
8 |
9 | Nes2A03::Nes2A03() { noise_seq.sequence = 0xDBDB; }
10 |
11 | Nes2A03::~Nes2A03() {}
12 |
13 | void Nes2A03::cpuWrite(uint16_t addr, uint8_t data) {
14 | switch (addr) {
15 | case 0x4000:
16 | switch ((data & 0xC0) >> 6) {
17 | case 0x00:
18 | pulse1_seq.new_sequence = 0b01000000;
19 | pulse1_osc.dutycycle = 0.125; // 占空比体积
20 | break;
21 | case 0x01:
22 | pulse1_seq.new_sequence = 0b01100000;
23 | pulse1_osc.dutycycle = 0.250;
24 | break;
25 | case 0x02:
26 | pulse1_seq.new_sequence = 0b01111000;
27 | pulse1_osc.dutycycle = 0.500;
28 | break;
29 | case 0x03:
30 | pulse1_seq.new_sequence = 0b10011111;
31 | pulse1_osc.dutycycle = 0.750;
32 | break;
33 | }
34 | pulse1_seq.sequence = pulse1_seq.new_sequence; // 更新占空比体积
35 | pulse1_halt = (data & 0x20); // envelope loop / length counter halt
36 | pulse1_env.volume = (data & 0x0F); // 音量
37 | pulse1_env.disable = (data & 0x10); // constant volume
38 | break;
39 |
40 | case 0x4001:
41 | pulse1_sweep.enabled = data & 0x80; // Enabled flag
42 | pulse1_sweep.period =
43 | (data & 0x70) >> 4; // The divider's period is P + 1 half-frames
44 | pulse1_sweep.down = data & 0x08; // 启用标志 Enabled flag
45 | pulse1_sweep.shift =
46 | data & 0x07; // 移位计数器 Shift count (number of bits)
47 | pulse1_sweep.reload = true; // ?
48 | break;
49 |
50 | case 0x4002:
51 | pulse1_seq.reload =
52 | (pulse1_seq.reload & 0xFF00) | data; // Pulse 1 timer Low 8 bits
53 | break;
54 |
55 | case 0x4003:
56 | pulse1_seq.reload = (uint16_t)((data & 0x07)) << 8 |
57 | (pulse1_seq.reload & 0x00FF); // timer High 3 bits
58 | pulse1_seq.timer = pulse1_seq.reload; // seq被赋值
59 | pulse1_seq.sequence = pulse1_seq.new_sequence; // 更新占空比体积
60 | pulse1_lc.counter =
61 | length_table[(data & 0xF8) >> 3]; // Pulse 2 length counter load
62 | pulse1_env.start = true; // ?
63 | break;
64 |
65 | case 0x4004:
66 | switch ((data & 0xC0) >> 6) {
67 | case 0x00:
68 | pulse2_seq.new_sequence = 0b01000000;
69 | pulse2_osc.dutycycle = 0.125;
70 | break;
71 | case 0x01:
72 | pulse2_seq.new_sequence = 0b01100000;
73 | pulse2_osc.dutycycle = 0.250;
74 | break;
75 | case 0x02:
76 | pulse2_seq.new_sequence = 0b01111000;
77 | pulse2_osc.dutycycle = 0.500;
78 | break;
79 | case 0x03:
80 | pulse2_seq.new_sequence = 0b10011111;
81 | pulse2_osc.dutycycle = 0.750;
82 | break;
83 | }
84 | pulse2_seq.sequence = pulse2_seq.new_sequence;
85 | pulse2_halt = (data & 0x20);
86 | pulse2_env.volume = (data & 0x0F);
87 | pulse2_env.disable = (data & 0x10);
88 | break;
89 |
90 | case 0x4005:
91 | pulse2_sweep.enabled = data & 0x80;
92 | pulse2_sweep.period = (data & 0x70) >> 4;
93 | pulse2_sweep.down = data & 0x08;
94 | pulse2_sweep.shift = data & 0x07;
95 | pulse2_sweep.reload = true;
96 | break;
97 |
98 | case 0x4006:
99 | pulse2_seq.reload = (pulse2_seq.reload & 0xFF00) | data;
100 | break;
101 |
102 | case 0x4007:
103 | pulse2_seq.reload =
104 | (uint16_t)((data & 0x07)) << 8 | (pulse2_seq.reload & 0x00FF);
105 | pulse2_seq.timer = pulse2_seq.reload;
106 | pulse2_seq.sequence = pulse2_seq.new_sequence;
107 | pulse2_lc.counter = length_table[(data & 0xF8) >> 3];
108 | pulse2_env.start = true;
109 |
110 | break;
111 |
112 | case 0x4008:
113 | break;
114 |
115 | case 0x400C:
116 | noise_env.volume = (data & 0x0F); // , volume/envelope
117 | noise_env.disable = (data & 0x10); // , constant volume
118 | noise_halt = (data & 0x20); // Envelope loop / length counter halt
119 | break;
120 |
121 | case 0x400E:
122 | // Loop noise ???
123 | switch (data & 0x0F) { // noise period (P)
124 | case 0x00:
125 | noise_seq.reload = 0;
126 | break;
127 | case 0x01:
128 | noise_seq.reload = 4;
129 | break;
130 | case 0x02:
131 | noise_seq.reload = 8;
132 | break;
133 | case 0x03:
134 | noise_seq.reload = 16;
135 | break;
136 | case 0x04:
137 | noise_seq.reload = 32;
138 | break;
139 | case 0x05:
140 | noise_seq.reload = 64;
141 | break;
142 | case 0x06:
143 | noise_seq.reload = 96;
144 | break;
145 | case 0x07:
146 | noise_seq.reload = 128;
147 | break;
148 | case 0x08:
149 | noise_seq.reload = 160;
150 | break;
151 | case 0x09:
152 | noise_seq.reload = 202;
153 | break;
154 | case 0x0A:
155 | noise_seq.reload = 254;
156 | break;
157 | case 0x0B:
158 | noise_seq.reload = 380;
159 | break;
160 | case 0x0C:
161 | noise_seq.reload = 508;
162 | break;
163 | case 0x0D:
164 | noise_seq.reload = 1016;
165 | break;
166 | case 0x0E:
167 | noise_seq.reload = 2034;
168 | break;
169 | case 0x0F:
170 | noise_seq.reload = 4068;
171 | break;
172 | }
173 | break;
174 |
175 | case 0x4015: // APU STATUS
176 | pulse1_enable = data & 0x01; // 脉冲通道1启用判断
177 | pulse2_enable = data & 0x02; // 脉冲通道2启用判断
178 | noise_enable = data & 0x04; // 噪声 启用判断
179 | // 还有DMC和三角启用判断,这里没有使用
180 | break;
181 |
182 | case 0x400F:
183 | pulse1_env.start = true;
184 | pulse2_env.start = true;
185 | noise_env.start = true;
186 | noise_lc.counter =
187 | length_table[(data & 0xF8) >>
188 | 3]; // 长度计数器负载 (L) Length counter load
189 | break;
190 | }
191 | }
192 |
193 | uint8_t Nes2A03::cpuRead(uint16_t addr) {
194 | uint8_t data = 0x00;
195 |
196 | if (addr == 0x4015) {
197 | // data |= (pulse1_lc.counter > 0) ? 0x01 : 0x00;
198 | // data |= (pulse2_lc.counter > 0) ? 0x02 : 0x00;
199 | // data |= (noise_lc.counter > 0) ? 0x04 : 0x00;
200 | }
201 |
202 | return data;
203 | }
204 |
205 | void Nes2A03::clock() {
206 | // Depending on the frame count, we set a flag to tell
207 | // us where we are in the sequence. Essentially, changes
208 | // to notes only occur at these intervals, meaning, in a
209 | // way, this is responsible for ensuring musical time is
210 | // maintained.
211 | bool bQuarterFrameClock = false;
212 | bool bHalfFrameClock = false;
213 |
214 | dGlobalTime += (0.3333333333 / 1789773);
215 |
216 | if (clock_counter % 6 == 0) {
217 | frame_clock_counter++;
218 |
219 | // 4-Step Sequence Mode
220 | if (frame_clock_counter == 3729) {
221 | bQuarterFrameClock = true;
222 | }
223 |
224 | if (frame_clock_counter == 7457) {
225 | bQuarterFrameClock = true;
226 | bHalfFrameClock = true;
227 | }
228 |
229 | if (frame_clock_counter == 11186) {
230 | bQuarterFrameClock = true;
231 | }
232 |
233 | if (frame_clock_counter == 14916) {
234 | bQuarterFrameClock = true;
235 | bHalfFrameClock = true;
236 | frame_clock_counter = 0;
237 | }
238 |
239 | // Update functional units
240 |
241 | // Quater frame "beats" adjust the volume envelope
242 | if (bQuarterFrameClock) {
243 | pulse1_env.clock(pulse1_halt);
244 | pulse2_env.clock(pulse2_halt);
245 | noise_env.clock(noise_halt);
246 | }
247 |
248 | // Half frame "beats" adjust the note length and
249 | // frequency sweepers
250 | if (bHalfFrameClock) {
251 | pulse1_lc.clock(pulse1_enable, pulse1_halt);
252 | pulse2_lc.clock(pulse2_enable, pulse2_halt);
253 | noise_lc.clock(noise_enable, noise_halt);
254 | pulse1_sweep.clock(pulse1_seq.reload, 0);
255 | pulse2_sweep.clock(pulse2_seq.reload, 1);
256 | }
257 |
258 | // if (bUseRawMode)
259 | {
260 | // Update Pulse1 Channel ================================
261 | pulse1_seq.clock(pulse1_enable, [](uint32_t &s) {
262 | // Shift right by 1 bit, wrapping around
263 | s = ((s & 0x0001) << 7) | ((s & 0x00FE) >> 1);
264 | });
265 |
266 | // pulse1_sample = (double)pulse1_seq.output;
267 | }
268 | // else
269 | {
270 | pulse1_osc.frequency =
271 | 1789773.0 / (16.0 * static_cast(pulse1_seq.reload + 1));
272 | pulse1_osc.amplitude = static_cast(pulse1_env.output - 1) / 16.0f;
273 | pulse1_sample = pulse1_osc.sample(dGlobalTime);
274 |
275 | if (pulse1_lc.counter > 0 && pulse1_seq.timer >= 8 &&
276 | !pulse1_sweep.mute && pulse1_env.output > 2)
277 | pulse1_output += (pulse1_sample - pulse1_output) * 0.5;
278 | else
279 | pulse1_output = 0;
280 | }
281 |
282 | // if (bUseRawMode)
283 | {
284 | // Update Pulse1 Channel ================================
285 | pulse2_seq.clock(pulse2_enable, [](uint32_t &s) {
286 | // Shift right by 1 bit, wrapping around
287 | s = ((s & 0x0001) << 7) | ((s & 0x00FE) >> 1);
288 | });
289 |
290 | // pulse2_sample = (double)pulse2_seq.output;
291 | }
292 | // else
293 | {
294 | pulse2_osc.frequency =
295 | 1789773.0 / (16.0 * static_cast(pulse2_seq.reload + 1));
296 | pulse2_osc.amplitude = static_cast(pulse2_env.output - 1) / 16.0;
297 | pulse2_sample = pulse2_osc.sample(dGlobalTime);
298 |
299 | if (pulse2_lc.counter > 0 && pulse2_seq.timer >= 8 &&
300 | !pulse2_sweep.mute && pulse2_env.output > 2)
301 | pulse2_output += (pulse2_sample - pulse2_output) * 0.5;
302 | else
303 | pulse2_output = 0;
304 | }
305 |
306 | noise_seq.clock(noise_enable, [](uint32_t &s) {
307 | s = (((s & 0x0001) ^ ((s & 0x0002) >> 1)) << 14) | ((s & 0x7FFF) >> 1);
308 | });
309 |
310 | if (noise_lc.counter > 0 && noise_seq.timer >= 8) {
311 | noise_output = static_cast(noise_seq.output) *
312 | (static_cast(noise_env.output - 1) / 16.0);
313 | }
314 |
315 | if (!pulse1_enable) pulse1_output = 0;
316 | if (!pulse2_enable) pulse2_output = 0;
317 | if (!noise_enable) noise_output = 0;
318 | }
319 |
320 | // Frequency sweepers change at high frequency
321 | pulse1_sweep.track(pulse1_seq.reload);
322 | pulse2_sweep.track(pulse2_seq.reload);
323 |
324 | pulse1_visual = (pulse1_enable && pulse1_env.output > 1 && !pulse1_sweep.mute)
325 | ? pulse1_seq.reload
326 | : 2047;
327 | pulse2_visual = (pulse2_enable && pulse2_env.output > 1 && !pulse2_sweep.mute)
328 | ? pulse2_seq.reload
329 | : 2047;
330 | noise_visual =
331 | (noise_enable && noise_env.output > 1) ? noise_seq.reload : 2047;
332 |
333 | clock_counter++;
334 | }
335 |
336 | double Nes2A03::GetOutputSample() {
337 | if (bUseRawMode) {
338 | return (pulse1_sample - 0.5) * 0.5 + (pulse2_sample - 0.5) * 0.5;
339 | } else {
340 | return ((1.0 * pulse1_output) - 0.8) * 0.1 +
341 | ((1.0 * pulse2_output) - 0.8) * 0.1 +
342 | ((2.0 * (noise_output - 0.5))) * 0.1;
343 | }
344 | }
345 |
346 | void Nes2A03::reset() {}
347 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Nes2A03.h:
--------------------------------------------------------------------------------
1 | /*
2 | olc::NES - APU
3 | "Thanks Dad for believing computers were gonna be a big deal..." -
4 | javidx9
5 |
6 | License (OLC-3)
7 | ~~~~~~~~~~~~~~~
8 |
9 | Copyright 2018-2019 OneLoneCoder.com
10 |
11 | Redistribution and use in source and binary forms, with or without
12 | modification, are permitted provided that the following conditions
13 | are met:
14 |
15 | 1. Redistributions or derivations of source code must retain the above
16 | copyright notice, this list of conditions and the following disclaimer.
17 |
18 | 2. Redistributions or derivative works in binary form must reproduce
19 | the above copyright notice. This list of conditions and the following
20 | disclaimer must be reproduced in the documentation and/or other
21 | materials provided with the distribution.
22 |
23 | 3. Neither the name of the copyright holder nor the names of its
24 | contributors may be used to endorse or promote products derived
25 | from this software without specific prior written permission.
26 |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 |
39 |
40 | Relevant Video: https://youtu.be/xdzOvpYPmGE
41 |
42 | Links
43 | ~~~~~
44 | YouTube: https://www.youtube.com/javidx9
45 | https://www.youtube.com/javidx9extra
46 | Discord: https://discord.gg/WhwHUMV
47 | Twitter: https://www.twitter.com/javidx9
48 | Twitch: https://www.twitch.tv/javidx9
49 | GitHub: https://www.github.com/onelonecoder
50 | Patreon: https://www.patreon.com/javidx9
51 | Homepage: https://www.onelonecoder.com
52 |
53 | Author
54 | ~~~~~~
55 | David Barr, aka javidx9, �OneLoneCoder 2019
56 | */
57 |
58 | /*
59 |
60 | IMPORTANT !!!!
61 |
62 | THIS CLASS IS VERY UNFINISHED
63 |
64 | */
65 | #pragma once
66 |
67 | #include
68 | #include
69 |
70 | class Nes2A03 {
71 | public:
72 | Nes2A03();
73 | ~Nes2A03();
74 |
75 | public:
76 | void cpuWrite(uint16_t addr, uint8_t data);
77 | uint8_t cpuRead(uint16_t addr);
78 | void clock();
79 | void reset();
80 |
81 | double GetOutputSample();
82 |
83 | private:
84 | uint32_t frame_clock_counter = 0;
85 | uint32_t clock_counter = 0;
86 | bool bUseRawMode = false;
87 |
88 | private:
89 | static uint8_t length_table[];
90 |
91 | private:
92 | // Sequencer Module
93 | // ~~~~~~~~~~~~~~~~
94 | // The purpose of the sequencer is to output a '1' after a given
95 | // interval. It does this by counting down from a start value,
96 | // when that value is < 0, it gets reset, and an internal "rotary"
97 | // buffer is shifted. The nature of ths shifted pattern is different
98 | // depending upon the channel, or module that requires sequencing.
99 | // For example, the square wave channels simply rotate the preset
100 | // sequence, but the noise channel needs to generate pseudo-random
101 | // outputs originating from the preset sequence.
102 | //
103 | // Consider a square wave channel. A preset sequence of 01010101
104 | // will output a 1 more freqently than 00010001, assuming we
105 | // always output the LSB. The speed of this output is also
106 | // governed by the timer counting down. The frequency is higher
107 | // for small timer values, and lower for larger. Increasing
108 | // the frequency of the output potentially increases the
109 | // audible frequency. In fact, this is how the pulse channels
110 | // fundamentally work. A "duty cycle" shape is loaded into the
111 | // sequencer and the timer is used to vary the pitch, yielding
112 | // notes.
113 |
114 | struct sequencer {
115 | uint32_t sequence = 0x00000000;
116 | uint32_t new_sequence = 0x00000000;
117 | uint16_t timer = 0x0000;
118 | uint16_t reload = 0x0000;
119 | uint8_t output = 0x00;
120 |
121 | // Pass in a lambda function to manipulate the sequence as required
122 | // by the owner of this sequencer module
123 | uint8_t clock(bool bEnable, std::function funcManip) {
124 | if (bEnable) {
125 | timer--;
126 | if (timer == 0xFFFF) {
127 | timer = reload;
128 | funcManip(sequence);
129 | output = sequence & 0x00000001;
130 | }
131 | }
132 | return output;
133 | }
134 | };
135 |
136 | struct lengthcounter {
137 | uint8_t counter = 0x00;
138 | uint8_t clock(bool bEnable, bool bHalt) {
139 | if (!bEnable)
140 | counter = 0;
141 | else if (counter > 0 && !bHalt)
142 | counter--;
143 | return counter;
144 | }
145 | };
146 |
147 | struct envelope {
148 | void clock(bool bLoop) {
149 | if (!start) {
150 | if (divider_count == 0) {
151 | divider_count = volume;
152 |
153 | if (decay_count == 0) {
154 | if (bLoop) {
155 | decay_count = 15;
156 | }
157 | } else
158 | decay_count--;
159 | } else
160 | divider_count--;
161 | } else {
162 | start = false;
163 | decay_count = 15;
164 | divider_count = volume;
165 | }
166 |
167 | if (disable) {
168 | output = volume;
169 | } else {
170 | output = decay_count;
171 | }
172 | }
173 |
174 | bool start = false;
175 | bool disable = false;
176 | uint16_t divider_count = 0;
177 | uint16_t volume = 0;
178 | uint16_t output = 0;
179 | uint16_t decay_count = 0;
180 | };
181 |
182 | struct oscpulse { // 脉冲
183 | double frequency = 0;
184 | double dutycycle = 0;
185 | double amplitude = 1;
186 | double pi = 3.14159;
187 | double harmonics = 20;
188 |
189 | double sample(double t) {
190 | double a = 0;
191 | double b = 0;
192 | double p = dutycycle * 2.0 * pi;
193 |
194 | auto approxsin = [](float t) {
195 | float j = t * 0.15915;
196 | j = j - static_cast(j);
197 | return 20.785f * j * (j - 0.5) * (j - 1.0f);
198 | };
199 |
200 | for (double n = 1; n < harmonics; n++) {
201 | double c = n * frequency * 2.0 * pi * t;
202 | a += -approxsin(c) / n;
203 | b += -approxsin(c - p * n) / n;
204 |
205 | // a += -sin(c) / n;
206 | // b += -sin(c - p * n) / n;
207 | }
208 |
209 | return (2.0 * amplitude / pi) * (a - b);
210 | }
211 | };
212 |
213 | struct sweeper {
214 | bool enabled = false;
215 | bool down = false;
216 | bool reload = false;
217 | uint8_t shift = 0x00;
218 | uint8_t timer = 0x00;
219 | uint8_t period = 0x00;
220 | uint16_t change = 0;
221 | bool mute = false;
222 |
223 | void track(uint16_t target) {
224 | if (enabled) {
225 | change = target >> shift;
226 | mute = (target < 8) || (target > 0x7FF);
227 | }
228 | }
229 |
230 | bool clock(uint16_t &target, bool channel) {
231 | bool changed = false;
232 | if (timer == 0 && enabled && shift > 0 && !mute) {
233 | if (target >= 8 && change < 0x07FF) {
234 | if (down) {
235 | target -= change - channel;
236 | } else {
237 | target += change;
238 | }
239 | changed = true;
240 | }
241 | }
242 |
243 | // if (enabled)
244 | {
245 | if (timer == 0 || reload) {
246 | timer = period;
247 | reload = false;
248 | } else
249 | timer--;
250 |
251 | mute = (target < 8) || (target > 0x7FF);
252 | }
253 |
254 | return changed;
255 | }
256 | };
257 |
258 | double dGlobalTime = 0.0;
259 |
260 | // Square Wave Pulse Channel 1
261 | bool pulse1_enable = false;
262 | bool pulse1_halt = false;
263 | double pulse1_sample = 0.0;
264 | double pulse1_output = 0.0;
265 | sequencer pulse1_seq;
266 | oscpulse pulse1_osc;
267 | envelope pulse1_env;
268 | lengthcounter pulse1_lc;
269 | sweeper pulse1_sweep;
270 |
271 | // Square Wave Pulse Channel 2
272 | bool pulse2_enable = false;
273 | bool pulse2_halt = false;
274 | double pulse2_sample = 0.0;
275 | double pulse2_output = 0.0;
276 | sequencer pulse2_seq;
277 | oscpulse pulse2_osc;
278 | envelope pulse2_env;
279 | lengthcounter pulse2_lc;
280 | sweeper pulse2_sweep;
281 |
282 | // Noise Channel
283 | bool noise_enable = false;
284 | bool noise_halt = false;
285 | envelope noise_env;
286 | lengthcounter noise_lc;
287 | sequencer noise_seq;
288 | double noise_sample = 0;
289 | double noise_output = 0;
290 |
291 | public:
292 | uint16_t pulse1_visual = 0;
293 | uint16_t pulse2_visual = 0;
294 | uint16_t noise_visual = 0;
295 | uint16_t triangle_visual = 0;
296 | };
297 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Nes2C02.h:
--------------------------------------------------------------------------------
1 | // Copyright [2020-2022]
2 |
3 | #pragma once
4 | #include
5 | #include
6 |
7 | #include "Cartridge.h"
8 | #include "olcPixelGameEngine.h"
9 | class Nes2C02 {
10 | public:
11 | Nes2C02();
12 | ~Nes2C02();
13 |
14 | private:
15 | uint8_t tblName[2][1024]; // 名称表
16 | uint8_t tblPattern[2][4096]; // 模式表
17 | uint8_t tblPalette[32]; // 调色板
18 |
19 | private:
20 | olc::Pixel palScreen[0x40]; // 颜色
21 | olc::Sprite sprScreen = olc::Sprite(256, 240); // 显示器
22 | olc::Sprite sprNameTable[2] = {
23 | olc::Sprite(256, 240), olc::Sprite(256, 240)}; // 读取需要显示的名称表
24 | olc::Sprite sprPatternTable[2] = {olc::Sprite(128, 128),
25 | olc::Sprite(128, 128)}; // 需要显示的模式表
26 | olc::Sprite sprSpriteTitle[26]; // 0-26个OAM中的精灵
27 |
28 | public:
29 | // 调试函数(打印各种信息)
30 | olc::Sprite &GetScreen(); // 屏幕
31 | olc::Sprite &GetNameTable(uint8_t i); // 获取名称表
32 | olc::Sprite &GetPatternTable(uint8_t i, uint8_t palette); // 获取模式表
33 | // olc::Sprite &GetSpriteTitle(uint8_t x, // 打印部分精灵OAM信息
34 | // uint8_t y, uint8_t title, uint8_t attr,
35 | // uint8_t palette, int i);
36 |
37 | olc::Pixel &GetColourFromPaletteRam(uint8_t palette,
38 | uint8_t pixel); // 获取调色后的pixel
39 |
40 | bool frame_complete = false; // 用来判断帧的绘制是否完成
41 |
42 | private:
43 | union {
44 | struct {
45 | uint8_t unused : 5;
46 | uint8_t sprite_overflow : 1;
47 | uint8_t sprite_zero_hit : 1;
48 | uint8_t vertical_blank : 1;
49 | };
50 |
51 | uint8_t reg;
52 | } status; // 状态寄存器
53 |
54 | union {
55 | struct {
56 | uint8_t grayscale : 1;
57 | uint8_t render_background_left : 1;
58 | uint8_t render_sprites_left : 1;
59 | uint8_t render_background : 1;
60 | uint8_t render_sprites : 1;
61 | uint8_t enhance_red : 1;
62 | uint8_t enhance_green : 1;
63 | uint8_t enhance_blue : 1;
64 | };
65 |
66 | uint8_t reg;
67 | } mask; // 掩码寄存器
68 |
69 | union PPUCTRL {
70 | struct {
71 | uint8_t nametable_x : 1;
72 | uint8_t nametable_y : 1;
73 | uint8_t increment_mode : 1;
74 | uint8_t pattern_sprite : 1;
75 | uint8_t pattern_background : 1;
76 | uint8_t sprite_size : 1;
77 | uint8_t slave_mode : 1; // unused
78 | uint8_t enable_nmi : 1;
79 | };
80 |
81 | uint8_t reg;
82 | } control; // 控制寄存器
83 |
84 | union loopy_register {
85 | // Credit to Loopy for working this out :D
86 | struct {
87 | uint16_t coarse_x : 5; // 0 - 31 nametable的 x 轴坐标
88 | uint16_t coarse_y : 5; // 0 - 31 nametable的 y 轴坐标
89 | uint16_t nametable_x : 1; //
90 | uint16_t nametable_y : 1;
91 | uint16_t fine_y : 3; // 0- 8 用来记录此 pixel 绘制的y 坐标,
92 | uint16_t unused : 1;
93 | };
94 |
95 | uint16_t reg = 0x0000;
96 | }; // ppu中的绘制寄存器
97 | // 将活动“指针”地址插入nametable以提取背景磁贴信息
98 | loopy_register vram_addr; // Active "pointer" address into nametable to
99 | // extract background tile info
100 | // 在不同时间将信息临时存储到“指针”中
101 | loopy_register tram_addr; // Temporary store of information to be
102 | // "transferred" into "pointer" at various times
103 |
104 | // 像素水平偏移
105 | uint8_t fine_x = 0x00;
106 |
107 | // ppu 内部寄存器
108 | uint8_t address_latch = 0x00;
109 | uint8_t ppu_data_buffer = 0x00;
110 |
111 | // 像素“点”位置信息
112 | int16_t scanline = 0; // 用来记录扫描的行数
113 | int16_t cycle = 0; // 记录扫描点的列数
114 |
115 | // 背景像素渲染信息
116 | uint8_t bg_next_tile_id = 0x00;
117 | uint8_t bg_next_tile_attrib = 0x00;
118 | uint8_t bg_next_tile_lsb = 0x00;
119 | uint8_t bg_next_tile_msb = 0x00;
120 | uint16_t bg_shifter_pattern_lo = 0x0000;
121 | uint16_t bg_shifter_pattern_hi = 0x0000;
122 | uint16_t bg_shifter_attrib_lo = 0x0000;
123 | uint16_t bg_shifter_attrib_hi = 0x0000;
124 |
125 | // 前景"精灵"渲染信息
126 | // OAM是PPU内部的附加内存。
127 | // 未通过任何总线连接。它
128 | // 存储在下一帧上绘制64个8x8(或8x16)瓷砖。
129 | struct sObjectAttributeEntry {
130 | uint8_t y; // 精灵在名称表的Y轴坐标 Y 1- 256
131 | uint8_t id; // 精灵在模式表中的ID号,一共两张表,
132 | uint8_t attribute; // 存放该精灵的title属性
133 | uint8_t x; // 精灵在名称表的Y轴坐标 X 1-240
134 | } OAM[64];
135 |
136 | // 当CPU手动通信时存储地址的寄存器
137 | // 通过PPU寄存器使用OAM。不常用,因为它
138 | // 非常慢,使用256字节的DMA传输。
139 | uint8_t oam_addr = 0x00;
140 |
141 | sObjectAttributeEntry spriteScanline[8]; // 在一行中,存放的最多8个精灵
142 | uint8_t sprite_count; // 在一行中的精灵数量
143 | uint8_t
144 | sprite_shifter_pattern_lo[8]; // 下一行将要绘制的精灵像素的低8个字节 信息
145 | uint8_t sprite_shifter_pattern_hi
146 | [8]; // 下一行将要绘制的精灵像素的高8个字节 信息
147 | // 这两个字节相加的结果组成一个8x8bit的像素图片
148 | // 0号精灵命中标志
149 | bool bSpriteZeroHitPossible = false;
150 | bool bSpriteZeroBeingRendered = false;
151 | bool odd_frame = false;
152 |
153 | public:
154 | // 上面的OAM可以方便地打包使用,但是DMA
155 | // 机制需要访问它,以便一次编写一个byte
156 | // 将OAM强转为字节指针,方便DMA直接传输
157 | uint8_t *pOAM = reinterpret_cast(OAM);
158 |
159 | public:
160 | // PPU与主线CPU进行通信函数
161 | uint8_t cpuRead(uint16_t addr, bool rdonly = false);
162 | void cpuWrite(uint16_t addr, uint8_t data);
163 |
164 | // 与PPU内部总线进行通信函数
165 | // 其中包含一些镜像地址需要转换
166 | uint8_t ppuRead(uint16_t addr, bool rdonly = false);
167 | void ppuWrite(uint16_t addr, uint8_t data);
168 |
169 | private:
170 | // 卡带
171 | std::shared_ptr cart;
172 |
173 | public:
174 | // 外部接口
175 | void ConnectCartridge(const std::shared_ptr &cartridge);
176 | void clock();
177 | void reset();
178 | bool nmi = false;
179 | };
180 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Nes6502.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Nes6502 - An emulation of the 6502/2A03 processor
3 | "Thanks Dad for believing computers were gonna be a big deal..." -
4 | javidx9
5 |
6 | License (OLC-3)
7 | ~~~~~~~~~~~~~~~
8 |
9 | Copyright 2018-2019 OneLoneCoder.com
10 |
11 | Redistribution and use in source and binary forms, with or without
12 | modification, are permitted provided that the following conditions
13 | are met:
14 |
15 | 1. Redistributions or derivations of source code must retain the above
16 | copyright notice, this list of conditions and the following disclaimer.
17 |
18 | 2. Redistributions or derivative works in binary form must reproduce
19 | the above copyright notice. This list of conditions and the following
20 | disclaimer must be reproduced in the documentation and/or other
21 | materials provided with the distribution.
22 |
23 | 3. Neither the name of the copyright holder nor the names of its
24 | contributors may be used to endorse or promote products derived
25 | from this software without specific prior written permission.
26 |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 |
39 | Background
40 | ~~~~~~~~~~
41 | I love this microprocessor. It was at the heart of two of my favourite
42 | machines, the BBC Micro, and the Nintendo Entertainment System, as well
43 | as countless others in that era. I learnt to program on the Model B, and
44 | I learnt to love games on the NES, so in many ways, this processor is
45 | why I am the way I am today.
46 |
47 | In February 2019, I decided to undertake a selfish personal project and
48 | build a NES emulator. Ive always wanted to, and as such I've avoided
49 | looking at source code for such things. This made making this a real
50 | personal challenge. I know its been done countless times, and very
51 | likely in far more clever and accurate ways than mine, but I'm proud of this.
52 |
53 | Datasheet: http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
54 |
55 | Files: Nes6502.h, Nes6502.cpp
56 |
57 | Relevant Video:
58 |
59 | Links
60 | ~~~~~
61 | YouTube: https://www.youtube.com/javidx9
62 | https://www.youtube.com/javidx9extra
63 | Discord: https://discord.gg/WhwHUMV
64 | Twitter: https://www.twitter.com/javidx9
65 | Twitch: https://www.twitch.tv/javidx9
66 | GitHub: https://www.github.com/onelonecoder
67 | Patreon: https://www.patreon.com/javidx9
68 | Homepage: https://www.onelonecoder.com
69 |
70 | Author
71 | ~~~~~~
72 | David Barr, aka javidx9, �OneLoneCoder 2019
73 | */
74 |
75 | #include "Nes6502.h"
76 |
77 | #include "Bus.h"
78 |
79 | // Constructor
80 | Nes6502::Nes6502() {
81 | // Assembles the translation table. It's big, it's ugly, but it yields a
82 | // convenient way to emulate the 6502. I'm certain there are some "code-golf"
83 | // strategies to reduce this but I've deliberately kept it verbose for study
84 | // and alteration
85 |
86 | // It is 16x16 entries. This gives 256 instructions. It is arranged to that
87 | // the bottom 4 bits of the instruction choose the column, and the top 4 bits
88 | // choose the row.
89 |
90 | // For convenience to get function pointers to members of this class, I'm
91 | // using this or else it will be much much larger :D
92 |
93 | // The table is one big initialiser list of initialiser lists...
94 | using a = Nes6502;
95 | lookup = {
96 | {"BRK", &a::BRK, &a::IMM, 7}, {"ORA", &a::ORA, &a::IZX, 6},
97 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
98 | {"???", &a::NOP, &a::IMP, 3}, {"ORA", &a::ORA, &a::ZP0, 3},
99 | {"ASL", &a::ASL, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5},
100 | {"PHP", &a::PHP, &a::IMP, 3}, {"ORA", &a::ORA, &a::IMM, 2},
101 | {"ASL", &a::ASL, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2},
102 | {"???", &a::NOP, &a::IMP, 4}, {"ORA", &a::ORA, &a::ABS, 4},
103 | {"ASL", &a::ASL, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6},
104 | {"BPL", &a::BPL, &a::REL, 2}, {"ORA", &a::ORA, &a::IZY, 5},
105 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
106 | {"???", &a::NOP, &a::IMP, 4}, {"ORA", &a::ORA, &a::ZPX, 4},
107 | {"ASL", &a::ASL, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6},
108 | {"CLC", &a::CLC, &a::IMP, 2}, {"ORA", &a::ORA, &a::ABY, 4},
109 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7},
110 | {"???", &a::NOP, &a::IMP, 4}, {"ORA", &a::ORA, &a::ABX, 4},
111 | {"ASL", &a::ASL, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7},
112 | {"JSR", &a::JSR, &a::ABS, 6}, {"AND", &a::AND, &a::IZX, 6},
113 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
114 | {"BIT", &a::BIT, &a::ZP0, 3}, {"AND", &a::AND, &a::ZP0, 3},
115 | {"ROL", &a::ROL, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5},
116 | {"PLP", &a::PLP, &a::IMP, 4}, {"AND", &a::AND, &a::IMM, 2},
117 | {"ROL", &a::ROL, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2},
118 | {"BIT", &a::BIT, &a::ABS, 4}, {"AND", &a::AND, &a::ABS, 4},
119 | {"ROL", &a::ROL, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6},
120 | {"BMI", &a::BMI, &a::REL, 2}, {"AND", &a::AND, &a::IZY, 5},
121 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
122 | {"???", &a::NOP, &a::IMP, 4}, {"AND", &a::AND, &a::ZPX, 4},
123 | {"ROL", &a::ROL, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6},
124 | {"SEC", &a::SEC, &a::IMP, 2}, {"AND", &a::AND, &a::ABY, 4},
125 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7},
126 | {"???", &a::NOP, &a::IMP, 4}, {"AND", &a::AND, &a::ABX, 4},
127 | {"ROL", &a::ROL, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7},
128 | {"RTI", &a::RTI, &a::IMP, 6}, {"EOR", &a::EOR, &a::IZX, 6},
129 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
130 | {"???", &a::NOP, &a::IMP, 3}, {"EOR", &a::EOR, &a::ZP0, 3},
131 | {"LSR", &a::LSR, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5},
132 | {"PHA", &a::PHA, &a::IMP, 3}, {"EOR", &a::EOR, &a::IMM, 2},
133 | {"LSR", &a::LSR, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2},
134 | {"JMP", &a::JMP, &a::ABS, 3}, {"EOR", &a::EOR, &a::ABS, 4},
135 | {"LSR", &a::LSR, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6},
136 | {"BVC", &a::BVC, &a::REL, 2}, {"EOR", &a::EOR, &a::IZY, 5},
137 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
138 | {"???", &a::NOP, &a::IMP, 4}, {"EOR", &a::EOR, &a::ZPX, 4},
139 | {"LSR", &a::LSR, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6},
140 | {"CLI", &a::CLI, &a::IMP, 2}, {"EOR", &a::EOR, &a::ABY, 4},
141 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7},
142 | {"???", &a::NOP, &a::IMP, 4}, {"EOR", &a::EOR, &a::ABX, 4},
143 | {"LSR", &a::LSR, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7},
144 | {"RTS", &a::RTS, &a::IMP, 6}, {"ADC", &a::ADC, &a::IZX, 6},
145 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
146 | {"???", &a::NOP, &a::IMP, 3}, {"ADC", &a::ADC, &a::ZP0, 3},
147 | {"ROR", &a::ROR, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5},
148 | {"PLA", &a::PLA, &a::IMP, 4}, {"ADC", &a::ADC, &a::IMM, 2},
149 | {"ROR", &a::ROR, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2},
150 | {"JMP", &a::JMP, &a::IND, 5}, {"ADC", &a::ADC, &a::ABS, 4},
151 | {"ROR", &a::ROR, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6},
152 | {"BVS", &a::BVS, &a::REL, 2}, {"ADC", &a::ADC, &a::IZY, 5},
153 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
154 | {"???", &a::NOP, &a::IMP, 4}, {"ADC", &a::ADC, &a::ZPX, 4},
155 | {"ROR", &a::ROR, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6},
156 | {"SEI", &a::SEI, &a::IMP, 2}, {"ADC", &a::ADC, &a::ABY, 4},
157 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7},
158 | {"???", &a::NOP, &a::IMP, 4}, {"ADC", &a::ADC, &a::ABX, 4},
159 | {"ROR", &a::ROR, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7},
160 | {"???", &a::NOP, &a::IMP, 2}, {"STA", &a::STA, &a::IZX, 6},
161 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 6},
162 | {"STY", &a::STY, &a::ZP0, 3}, {"STA", &a::STA, &a::ZP0, 3},
163 | {"STX", &a::STX, &a::ZP0, 3}, {"???", &a::XXX, &a::IMP, 3},
164 | {"DEY", &a::DEY, &a::IMP, 2}, {"???", &a::NOP, &a::IMP, 2},
165 | {"TXA", &a::TXA, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2},
166 | {"STY", &a::STY, &a::ABS, 4}, {"STA", &a::STA, &a::ABS, 4},
167 | {"STX", &a::STX, &a::ABS, 4}, {"???", &a::XXX, &a::IMP, 4},
168 | {"BCC", &a::BCC, &a::REL, 2}, {"STA", &a::STA, &a::IZY, 6},
169 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 6},
170 | {"STY", &a::STY, &a::ZPX, 4}, {"STA", &a::STA, &a::ZPX, 4},
171 | {"STX", &a::STX, &a::ZPY, 4}, {"???", &a::XXX, &a::IMP, 4},
172 | {"TYA", &a::TYA, &a::IMP, 2}, {"STA", &a::STA, &a::ABY, 5},
173 | {"TXS", &a::TXS, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 5},
174 | {"???", &a::NOP, &a::IMP, 5}, {"STA", &a::STA, &a::ABX, 5},
175 | {"???", &a::XXX, &a::IMP, 5}, {"???", &a::XXX, &a::IMP, 5},
176 | {"LDY", &a::LDY, &a::IMM, 2}, {"LDA", &a::LDA, &a::IZX, 6},
177 | {"LDX", &a::LDX, &a::IMM, 2}, {"???", &a::XXX, &a::IMP, 6},
178 | {"LDY", &a::LDY, &a::ZP0, 3}, {"LDA", &a::LDA, &a::ZP0, 3},
179 | {"LDX", &a::LDX, &a::ZP0, 3}, {"???", &a::XXX, &a::IMP, 3},
180 | {"TAY", &a::TAY, &a::IMP, 2}, {"LDA", &a::LDA, &a::IMM, 2},
181 | {"TAX", &a::TAX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2},
182 | {"LDY", &a::LDY, &a::ABS, 4}, {"LDA", &a::LDA, &a::ABS, 4},
183 | {"LDX", &a::LDX, &a::ABS, 4}, {"???", &a::XXX, &a::IMP, 4},
184 | {"BCS", &a::BCS, &a::REL, 2}, {"LDA", &a::LDA, &a::IZY, 5},
185 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 5},
186 | {"LDY", &a::LDY, &a::ZPX, 4}, {"LDA", &a::LDA, &a::ZPX, 4},
187 | {"LDX", &a::LDX, &a::ZPY, 4}, {"???", &a::XXX, &a::IMP, 4},
188 | {"CLV", &a::CLV, &a::IMP, 2}, {"LDA", &a::LDA, &a::ABY, 4},
189 | {"TSX", &a::TSX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 4},
190 | {"LDY", &a::LDY, &a::ABX, 4}, {"LDA", &a::LDA, &a::ABX, 4},
191 | {"LDX", &a::LDX, &a::ABY, 4}, {"???", &a::XXX, &a::IMP, 4},
192 | {"CPY", &a::CPY, &a::IMM, 2}, {"CMP", &a::CMP, &a::IZX, 6},
193 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
194 | {"CPY", &a::CPY, &a::ZP0, 3}, {"CMP", &a::CMP, &a::ZP0, 3},
195 | {"DEC", &a::DEC, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5},
196 | {"INY", &a::INY, &a::IMP, 2}, {"CMP", &a::CMP, &a::IMM, 2},
197 | {"DEX", &a::DEX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2},
198 | {"CPY", &a::CPY, &a::ABS, 4}, {"CMP", &a::CMP, &a::ABS, 4},
199 | {"DEC", &a::DEC, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6},
200 | {"BNE", &a::BNE, &a::REL, 2}, {"CMP", &a::CMP, &a::IZY, 5},
201 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
202 | {"???", &a::NOP, &a::IMP, 4}, {"CMP", &a::CMP, &a::ZPX, 4},
203 | {"DEC", &a::DEC, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6},
204 | {"CLD", &a::CLD, &a::IMP, 2}, {"CMP", &a::CMP, &a::ABY, 4},
205 | {"NOP", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7},
206 | {"???", &a::NOP, &a::IMP, 4}, {"CMP", &a::CMP, &a::ABX, 4},
207 | {"DEC", &a::DEC, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7},
208 | {"CPX", &a::CPX, &a::IMM, 2}, {"SBC", &a::SBC, &a::IZX, 6},
209 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
210 | {"CPX", &a::CPX, &a::ZP0, 3}, {"SBC", &a::SBC, &a::ZP0, 3},
211 | {"INC", &a::INC, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5},
212 | {"INX", &a::INX, &a::IMP, 2}, {"SBC", &a::SBC, &a::IMM, 2},
213 | {"NOP", &a::NOP, &a::IMP, 2}, {"???", &a::SBC, &a::IMP, 2},
214 | {"CPX", &a::CPX, &a::ABS, 4}, {"SBC", &a::SBC, &a::ABS, 4},
215 | {"INC", &a::INC, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6},
216 | {"BEQ", &a::BEQ, &a::REL, 2}, {"SBC", &a::SBC, &a::IZY, 5},
217 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8},
218 | {"???", &a::NOP, &a::IMP, 4}, {"SBC", &a::SBC, &a::ZPX, 4},
219 | {"INC", &a::INC, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6},
220 | {"SED", &a::SED, &a::IMP, 2}, {"SBC", &a::SBC, &a::ABY, 4},
221 | {"NOP", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7},
222 | {"???", &a::NOP, &a::IMP, 4}, {"SBC", &a::SBC, &a::ABX, 4},
223 | {"INC", &a::INC, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7},
224 | };
225 | }
226 |
227 | Nes6502::~Nes6502() {
228 | // Destructor - has nothing to do
229 | }
230 |
231 | ///////////////////////////////////////////////////////////////////////////////
232 | // BUS CONNECTIVITY
233 |
234 | // Reads an 8-bit byte from the bus, located at the specified 16-bit address
235 | uint8_t Nes6502::read(uint16_t a) {
236 | // In normal operation "read only" is set to false. This may seem odd. Some
237 | // devices on the bus may change state when they are read from, and this
238 | // is intentional under normal circumstances. However the disassembler will
239 | // want to read the data at an address without changing the state of the
240 | // devices on the bus
241 | return bus->cpuRead(a, false);
242 | }
243 |
244 | // Writes a byte to the bus at the specified address
245 | void Nes6502::write(uint16_t a, uint8_t d) { bus->cpuWrite(a, d); }
246 |
247 | ///////////////////////////////////////////////////////////////////////////////
248 | // EXTERNAL INPUTS
249 |
250 | // Forces the 6502 into a known state. This is hard-wired inside the CPU. The
251 | // registers are set to 0x00, the status register is cleared except for unused
252 | // bit which remains at 1. An absolute address is read from location 0xFFFC
253 | // which contains a second address that the program counter is set to. This
254 | // allows the programmer to jump to a known and programmable location in the
255 | // memory to start executing from. Typically the programmer would set the value
256 | // at location 0xFFFC at compile time.
257 | void Nes6502::reset() {
258 | // Get address to set program counter to
259 | addr_abs = 0xFFFC;
260 | uint16_t lo = read(addr_abs + 0);
261 | uint16_t hi = read(addr_abs + 1);
262 |
263 | // Set it
264 | pc = (hi << 8) | lo;
265 |
266 | // Reset internal registers
267 | a = 0;
268 | x = 0;
269 | y = 0;
270 | stkp = 0xFD;
271 | status = 0x00 | U;
272 |
273 | // Clear internal helper variables
274 | addr_rel = 0x0000;
275 | addr_abs = 0x0000;
276 | fetched = 0x00;
277 |
278 | // Reset takes time
279 | cycles = 8;
280 | }
281 |
282 | // Interrupt requests are a complex operation and only happen if the
283 | // "disable interrupt" flag is 0. IRQs can happen at any time, but
284 | // you dont want them to be destructive to the operation of the running
285 | // program. Therefore the current instruction is allowed to finish
286 | // (which I facilitate by doing the whole thing when cycles == 0) and
287 | // then the current program counter is stored on the stack. Then the
288 | // current status register is stored on the stack. When the routine
289 | // that services the interrupt has finished, the status register
290 | // and program counter can be restored to how they where before it
291 | // occurred. This is impemented by the "RTI" instruction. Once the IRQ
292 | // has happened, in a similar way to a reset, a programmable address
293 | // is read form hard coded location 0xFFFE, which is subsequently
294 | // set to the program counter.
295 | void Nes6502::irq() {
296 | // If interrupts are allowed
297 | if (GetFlag(I) == 0) {
298 | // Push the program counter to the stack. It's 16-bits dont
299 | // forget so that takes two pushes
300 | write(0x0100 + stkp, (pc >> 8) & 0x00FF);
301 | stkp--;
302 | write(0x0100 + stkp, pc & 0x00FF);
303 | stkp--;
304 |
305 | // Then Push the status register to the stack
306 | SetFlag(B, 0);
307 | SetFlag(U, 1);
308 | SetFlag(I, 1);
309 | write(0x0100 + stkp, status);
310 | stkp--;
311 |
312 | // Read new program counter location from fixed address
313 | addr_abs = 0xFFFE;
314 | uint16_t lo = read(addr_abs + 0);
315 | uint16_t hi = read(addr_abs + 1);
316 | pc = (hi << 8) | lo;
317 |
318 | // IRQs take time
319 | cycles = 7;
320 | }
321 | }
322 |
323 | // A Non-Maskable Interrupt cannot be ignored. It behaves in exactly the
324 | // same way as a regular IRQ, but reads the new program counter address
325 | // form location 0xFFFA.
326 | void Nes6502::nmi() {
327 | write(0x0100 + stkp, (pc >> 8) & 0x00FF);
328 | stkp--;
329 | write(0x0100 + stkp, pc & 0x00FF);
330 | stkp--;
331 |
332 | SetFlag(B, 0);
333 | SetFlag(U, 1);
334 | SetFlag(I, 1);
335 | write(0x0100 + stkp, status);
336 | stkp--;
337 |
338 | addr_abs = 0xFFFA;
339 | uint16_t lo = read(addr_abs + 0);
340 | uint16_t hi = read(addr_abs + 1);
341 | pc = (hi << 8) | lo;
342 |
343 | cycles = 8;
344 | }
345 |
346 | // Perform one clock cycles worth of emulation
347 | void Nes6502::clock() {
348 | // Each instruction requires a variable number of clock cycles to execute.
349 | // In my emulation, I only care about the final result and so I perform
350 | // the entire computation in one hit. In hardware, each clock cycle would
351 | // perform "microcode" style transformations of the CPUs state.
352 | //
353 | // To remain compliant with connected devices, it's important that the
354 | // emulation also takes "time" in order to execute instructions, so I
355 | // implement that delay by simply counting down the cycles required by
356 | // the instruction. When it reaches 0, the instruction is complete, and
357 | // the next one is ready to be executed.
358 | if (cycles == 0) {
359 | // Read next instruction byte. This 8-bit value is used to index
360 | // the translation table to get the relevant information about
361 | // how to implement the instruction
362 | opcode = read(pc);
363 |
364 | #ifdef LOGMODE
365 | uint16_t log_pc = pc;
366 | #endif
367 |
368 | // Always set the unused status flag bit to 1
369 | SetFlag(U, true);
370 |
371 | // Increment program counter, we read the opcode byte
372 | pc++;
373 |
374 | // Get Starting number of cycles
375 | cycles = lookup[opcode].cycles;
376 |
377 | // Perform fetch of intermmediate data using the
378 | // required addressing mode
379 | uint8_t additional_cycle1 = (this->*lookup[opcode].addrmode)();
380 |
381 | // Perform operation
382 | uint8_t additional_cycle2 = (this->*lookup[opcode].operate)();
383 |
384 | // The addressmode and opcode may have altered the number
385 | // of cycles this instruction requires before its completed
386 | cycles += (additional_cycle1 & additional_cycle2);
387 |
388 | // Always set the unused status flag bit to 1
389 | SetFlag(U, true);
390 |
391 | #ifdef LOGMODE
392 | // This logger dumps every cycle the entire processor state for analysis.
393 | // This can be used for debugging the emulation, but has little utility
394 | // during emulation. Its also very slow, so only use if you have to.
395 | if (logfile == nullptr) logfile = fopen("Nes6502.txt", "wt");
396 | if (logfile != nullptr) {
397 | fprintf(logfile,
398 | "%10d:%02d PC:%04X %s A:%02X X:%02X Y:%02X %s%s%s%s%s%s%s%s "
399 | "STKP:%02X\n",
400 | clock_count, 0, log_pc, "XXX", a, x, y, GetFlag(N) ? "N" : ".",
401 | GetFlag(V) ? "V" : ".", GetFlag(U) ? "U" : ".",
402 | GetFlag(B) ? "B" : ".", GetFlag(D) ? "D" : ".",
403 | GetFlag(I) ? "I" : ".", GetFlag(Z) ? "Z" : ".",
404 | GetFlag(C) ? "C" : ".", stkp);
405 | }
406 | #endif
407 | }
408 |
409 | // Increment global clock count - This is actually unused unless logging is
410 | // enabled but I've kept it in because its a handy watch variable for
411 | // debugging
412 | clock_count++;
413 |
414 | // Decrement the number of cycles remaining for this instruction
415 | cycles--;
416 | }
417 |
418 | ///////////////////////////////////////////////////////////////////////////////
419 | // FLAG FUNCTIONS
420 |
421 | // Returns the value of a specific bit of the status register
422 | uint8_t Nes6502::GetFlag(FLAGS6502 f) { return ((status & f) > 0) ? 1 : 0; }
423 |
424 | // Sets or clears a specific bit of the status register
425 | void Nes6502::SetFlag(FLAGS6502 f, bool v) {
426 | if (v)
427 | status |= f;
428 | else
429 | status &= ~f;
430 | }
431 |
432 | ///////////////////////////////////////////////////////////////////////////////
433 | // ADDRESSING MODES
434 |
435 | // The 6502 can address between 0x0000 - 0xFFFF. The high byte is often referred
436 | // to as the "page", and the low byte is the offset into that page. This implies
437 | // there are 256 pages, each containing 256 bytes.
438 | //
439 | // Several addressing modes have the potential to require an additional clock
440 | // cycle if they cross a page boundary. This is combined with several
441 | // instructions that enable this additional clock cycle. So each addressing
442 | // function returns a flag saying it has potential, as does each instruction. If
443 | // both instruction and address function return 1, then an additional clock
444 | // cycle is required.
445 |
446 | // Address Mode: Implied
447 | // There is no additional data required for this instruction. The instruction
448 | // does something very simple like like sets a status bit. However, we will
449 | // target the accumulator, for instructions like PHA
450 | uint8_t Nes6502::IMP() {
451 | fetched = a;
452 | return 0;
453 | }
454 |
455 | // Address Mode: Immediate
456 | // The instruction expects the next byte to be used as a value, so we'll prep
457 | // the read address to point to the next byte
458 | uint8_t Nes6502::IMM() {
459 | addr_abs = pc++;
460 | return 0;
461 | }
462 |
463 | // Address Mode: Zero Page
464 | // To save program bytes, zero page addressing allows you to absolutely address
465 | // a location in first 0xFF bytes of address range. Clearly this only requires
466 | // one byte instead of the usual two.
467 | uint8_t Nes6502::ZP0() {
468 | addr_abs = read(pc);
469 | pc++;
470 | addr_abs &= 0x00FF;
471 | return 0;
472 | }
473 |
474 | // Address Mode: Zero Page with X Offset
475 | // Fundamentally the same as Zero Page addressing, but the contents of the X
476 | // Register is added to the supplied single byte address. This is useful for
477 | // iterating through ranges within the first page.
478 | uint8_t Nes6502::ZPX() {
479 | addr_abs = (read(pc) + x);
480 | pc++;
481 | addr_abs &= 0x00FF;
482 | return 0;
483 | }
484 |
485 | // Address Mode: Zero Page with Y Offset
486 | // Same as above but uses Y Register for offset
487 | uint8_t Nes6502::ZPY() {
488 | addr_abs = (read(pc) + y);
489 | pc++;
490 | addr_abs &= 0x00FF;
491 | return 0;
492 | }
493 |
494 | // Address Mode: Relative
495 | // This address mode is exclusive to branch instructions. The address
496 | // must reside within -128 to +127 of the branch instruction, i.e.
497 | // you cant directly branch to any address in the addressable range.
498 | uint8_t Nes6502::REL() {
499 | addr_rel = read(pc);
500 | pc++;
501 | if (addr_rel & 0x80) addr_rel |= 0xFF00;
502 | return 0;
503 | }
504 |
505 | // Address Mode: Absolute
506 | // A full 16-bit address is loaded and used
507 | uint8_t Nes6502::ABS() {
508 | uint16_t lo = read(pc);
509 | pc++;
510 | uint16_t hi = read(pc);
511 | pc++;
512 |
513 | addr_abs = (hi << 8) | lo;
514 |
515 | return 0;
516 | }
517 |
518 | // Address Mode: Absolute with X Offset
519 | // Fundamentally the same as absolute addressing, but the contents of the X
520 | // Register is added to the supplied two byte address. If the resulting address
521 | // changes the page, an additional clock cycle is required
522 | uint8_t Nes6502::ABX() {
523 | uint16_t lo = read(pc);
524 | pc++;
525 | uint16_t hi = read(pc);
526 | pc++;
527 |
528 | addr_abs = (hi << 8) | lo;
529 | addr_abs += x;
530 |
531 | if ((addr_abs & 0xFF00) != (hi << 8))
532 | return 1;
533 | else
534 | return 0;
535 | }
536 |
537 | // Address Mode: Absolute with Y Offset
538 | // Fundamentally the same as absolute addressing, but the contents of the Y
539 | // Register is added to the supplied two byte address. If the resulting address
540 | // changes the page, an additional clock cycle is required
541 | uint8_t Nes6502::ABY() {
542 | uint16_t lo = read(pc);
543 | pc++;
544 | uint16_t hi = read(pc);
545 | pc++;
546 |
547 | addr_abs = (hi << 8) | lo;
548 | addr_abs += y;
549 |
550 | if ((addr_abs & 0xFF00) != (hi << 8))
551 | return 1;
552 | else
553 | return 0;
554 | }
555 |
556 | // Note: The next 3 address modes use indirection (aka Pointers!)
557 |
558 | // Address Mode: Indirect
559 | // The supplied 16-bit address is read to get the actual 16-bit address. This is
560 | // instruction is unusual in that it has a bug in the hardware! To emulate its
561 | // function accurately, we also need to emulate this bug. If the low byte of the
562 | // supplied address is 0xFF, then to read the high byte of the actual address
563 | // we need to cross a page boundary. This doesnt actually work on the chip as
564 | // designed, instead it wraps back around in the same page, yielding an
565 | // invalid actual address
566 | uint8_t Nes6502::IND() {
567 | uint16_t ptr_lo = read(pc);
568 | pc++;
569 | uint16_t ptr_hi = read(pc);
570 | pc++;
571 |
572 | uint16_t ptr = (ptr_hi << 8) | ptr_lo;
573 |
574 | if (ptr_lo == 0x00FF) { // Simulate page boundary hardware bug
575 | addr_abs = (read(ptr & 0xFF00) << 8) | read(ptr + 0);
576 | } else { // Behave normally
577 | addr_abs = (read(ptr + 1) << 8) | read(ptr + 0);
578 | }
579 |
580 | return 0;
581 | }
582 |
583 | // Address Mode: Indirect X
584 | // The supplied 8-bit address is offset by X Register to index
585 | // a location in page 0x00. The actual 16-bit address is read
586 | // from this location
587 | uint8_t Nes6502::IZX() {
588 | uint16_t t = read(pc);
589 | pc++;
590 |
591 | uint16_t lo = read((uint16_t)(t + (uint16_t)x) & 0x00FF);
592 | uint16_t hi = read((uint16_t)(t + (uint16_t)x + 1) & 0x00FF);
593 |
594 | addr_abs = (hi << 8) | lo;
595 |
596 | return 0;
597 | }
598 |
599 | // Address Mode: Indirect Y
600 | // The supplied 8-bit address indexes a location in page 0x00. From
601 | // here the actual 16-bit address is read, and the contents of
602 | // Y Register is added to it to offset it. If the offset causes a
603 | // change in page then an additional clock cycle is required.
604 | uint8_t Nes6502::IZY() {
605 | uint16_t t = read(pc);
606 | pc++;
607 |
608 | uint16_t lo = read(t & 0x00FF);
609 | uint16_t hi = read((t + 1) & 0x00FF);
610 |
611 | addr_abs = (hi << 8) | lo;
612 | addr_abs += y;
613 |
614 | if ((addr_abs & 0xFF00) != (hi << 8))
615 | return 1;
616 | else
617 | return 0;
618 | }
619 |
620 | // This function sources the data used by the instruction into
621 | // a convenient numeric variable. Some instructions dont have to
622 | // fetch data as the source is implied by the instruction. For example
623 | // "INX" increments the X register. There is no additional data
624 | // required. For all other addressing modes, the data resides at
625 | // the location held within addr_abs, so it is read from there.
626 | // Immediate adress mode exploits this slightly, as that has
627 | // set addr_abs = pc + 1, so it fetches the data from the
628 | // next byte for example "LDA $FF" just loads the accumulator with
629 | // 256, i.e. no far reaching memory fetch is required. "fetched"
630 | // is a variable global to the CPU, and is set by calling this
631 | // function. It also returns it for convenience.
632 | uint8_t Nes6502::fetch() {
633 | if (!(lookup[opcode].addrmode == &Nes6502::IMP)) fetched = read(addr_abs);
634 | return fetched;
635 | }
636 |
637 | ///////////////////////////////////////////////////////////////////////////////
638 | // INSTRUCTION IMPLEMENTATIONS
639 |
640 | // Note: Ive started with the two most complicated instructions to emulate,
641 | // which ironically is addition and subtraction! Ive tried to include a detailed
642 | // explanation as to why they are so complex, yet so fundamental. Im also NOT
643 | // going to do this through the explanation of 1 and 2's complement.
644 |
645 | // Instruction: Add with Carry In
646 | // Function: A = A + M + C
647 | // Flags Out: C, V, N, Z
648 | //
649 | // Explanation:
650 | // The purpose of this function is to add a value to the accumulator and a carry
651 | // bit. If the result is > 255 there is an overflow setting the carry bit. Ths
652 | // allows you to chain together ADC instructions to add numbers larger than
653 | // 8-bits. This in itself is simple, however the 6502 supports the concepts of
654 | // Negativity/Positivity and Signed Overflow.
655 | //
656 | // 10000100 = 128 + 4 = 132 in normal circumstances, we know this as unsigned
657 | // and it allows us to represent numbers between 0 and 255 (given 8 bits). The
658 | // 6502 can also interpret this word as something else if we assume those 8 bits
659 | // represent the range -128 to +127, i.e. it has become signed.
660 | //
661 | // Since 132 > 127, it effectively wraps around, through -128, to -124. This
662 | // wraparound is called overflow, and this is a useful to know as it indicates
663 | // that the calculation has gone outside the permissable range, and therefore no
664 | // longer makes numeric sense.
665 | //
666 | // Note the implementation of ADD is the same in binary, this is just about how
667 | // the numbers are represented, so the word 10000100 can be both -124 and 132
668 | // depending upon the context the programming is using it in. We can prove this!
669 | //
670 | // 10000100 = 132 or -124
671 | // +00010001 = + 17 + 17
672 | // ======== === === See, both are valid additions, but our
673 | // interpretation of 10010101 = 149 or -107 the context changes the
674 | // value, not the hardware!
675 | //
676 | // In principle under the -128 to 127 range:
677 | // 10000000 = -128, 11111111 = -1, 00000000 = 0, 00000000 = +1, 01111111 = +127
678 | // therefore negative numbers have the most significant set, positive numbers do
679 | // not
680 | //
681 | // To assist us, the 6502 can set the overflow flag, if the result of the
682 | // addition has wrapped around. V <- ~(A^M) & A^(A+M+C) :D lol, let's work out
683 | // why!
684 | //
685 | // Let's suppose we have A = 30, M = 10 and C = 0
686 | // A = 30 = 00011110
687 | // M = 10 = 00001010+
688 | // RESULT = 40 = 00101000
689 | //
690 | // Here we have not gone out of range. The resulting significant bit has not
691 | // changed. So let's make a truth table to understand when overflow has
692 | // occurred. Here I take the MSB of each component, where R is RESULT.
693 | //
694 | // A M R | V | A^R | A^M |~(A^M) |
695 | // 0 0 0 | 0 | 0 | 0 | 1 |
696 | // 0 0 1 | 1 | 1 | 0 | 1 |
697 | // 0 1 0 | 0 | 0 | 1 | 0 |
698 | // 0 1 1 | 0 | 1 | 1 | 0 | so V = ~(A^M) & (A^R)
699 | // 1 0 0 | 0 | 1 | 1 | 0 |
700 | // 1 0 1 | 0 | 0 | 1 | 0 |
701 | // 1 1 0 | 1 | 1 | 0 | 1 |
702 | // 1 1 1 | 0 | 0 | 0 | 1 |
703 | //
704 | // We can see how the above equation calculates V, based on A, M and R. V was
705 | // chosen based on the following hypothesis:
706 | // Positive Number + Positive Number = Negative Result -> Overflow
707 | // Negative Number + Negative Number = Positive Result -> Overflow
708 | // Positive Number + Negative Number = Either Result -> Cannot Overflow
709 | // Positive Number + Positive Number = Positive Result -> OK! No Overflow
710 | // Negative Number + Negative Number = Negative Result -> OK! NO Overflow
711 |
712 | uint8_t Nes6502::ADC() {
713 | // Grab the data that we are adding to the accumulator
714 | fetch();
715 |
716 | // Add is performed in 16-bit domain for emulation to capture any
717 | // carry bit, which will exist in bit 8 of the 16-bit word
718 | temp = (uint16_t)a + (uint16_t)fetched + (uint16_t)GetFlag(C);
719 |
720 | // The carry flag out exists in the high byte bit 0
721 | SetFlag(C, temp > 255);
722 |
723 | // The Zero flag is set if the result is 0
724 | SetFlag(Z, (temp & 0x00FF) == 0);
725 |
726 | // The signed Overflow flag is set based on all that up there! :D
727 | SetFlag(
728 | V, (~((uint16_t)a ^ (uint16_t)fetched) & ((uint16_t)a ^ (uint16_t)temp)) &
729 | 0x0080);
730 |
731 | // The negative flag is set to the most significant bit of the result
732 | SetFlag(N, temp & 0x80);
733 |
734 | // Load the result into the accumulator (it's 8-bit dont forget!)
735 | a = temp & 0x00FF;
736 |
737 | // This instruction has the potential to require an additional clock cycle
738 | return 1;
739 | }
740 |
741 | // Instruction: Subtraction with Borrow In
742 | // Function: A = A - M - (1 - C)
743 | // Flags Out: C, V, N, Z
744 | //
745 | // Explanation:
746 | // Given the explanation for ADC above, we can reorganise our data
747 | // to use the same computation for addition, for subtraction by multiplying
748 | // the data by -1, i.e. make it negative
749 | //
750 | // A = A - M - (1 - C) -> A = A + -1 * (M - (1 - C)) -> A = A + (-M + 1 + C)
751 | //
752 | // To make a signed positive number negative, we can invert the bits and add 1
753 | // (OK, I lied, a little bit of 1 and 2s complement :P)
754 | //
755 | // 5 = 00000101
756 | // -5 = 11111010 + 00000001 = 11111011 (or 251 in our 0 to 255 range)
757 | //
758 | // The range is actually unimportant, because if I take the value 15, and add
759 | // 251 to it, given we wrap around at 256, the result is 10, so it has
760 | // effectively subtracted 5, which was the original intention. (15 + 251) % 256
761 | // = 10
762 | //
763 | // Note that the equation above used (1-C), but this got converted to + 1 + C.
764 | // This means we already have the +1, so all we need to do is invert the bits
765 | // of M, the data(!) therfore we can simply add, exactly the same way we did
766 | // before.
767 |
768 | uint8_t Nes6502::SBC() {
769 | fetch();
770 |
771 | // Operating in 16-bit domain to capture carry out
772 |
773 | // We can invert the bottom 8 bits with bitwise xor
774 | uint16_t value = ((uint16_t)fetched) ^ 0x00FF;
775 |
776 | // Notice this is exactly the same as addition from here!
777 | temp = (uint16_t)a + value + (uint16_t)GetFlag(C);
778 | SetFlag(C, temp & 0xFF00);
779 | SetFlag(Z, ((temp & 0x00FF) == 0));
780 | SetFlag(V, (temp ^ (uint16_t)a) & (temp ^ value) & 0x0080);
781 | SetFlag(N, temp & 0x0080);
782 | a = temp & 0x00FF;
783 | return 1;
784 | }
785 |
786 | // OK! Complicated operations are done! the following are much simpler
787 | // and conventional. The typical order of events is:
788 | // 1) Fetch the data you are working with
789 | // 2) Perform calculation
790 | // 3) Store the result in desired place
791 | // 4) Set Flags of the status register
792 | // 5) Return if instruction has potential to require additional
793 | // clock cycle
794 |
795 | // Instruction: Bitwise Logic AND
796 | // Function: A = A & M
797 | // Flags Out: N, Z
798 | uint8_t Nes6502::AND() {
799 | fetch();
800 | a = a & fetched;
801 | SetFlag(Z, a == 0x00);
802 | SetFlag(N, a & 0x80);
803 | return 1;
804 | }
805 |
806 | // Instruction: Arithmetic Shift Left
807 | // Function: A = C <- (A << 1) <- 0
808 | // Flags Out: N, Z, C
809 | uint8_t Nes6502::ASL() {
810 | fetch();
811 | temp = (uint16_t)fetched << 1;
812 | SetFlag(C, (temp & 0xFF00) > 0);
813 | SetFlag(Z, (temp & 0x00FF) == 0x00);
814 | SetFlag(N, temp & 0x80);
815 | if (lookup[opcode].addrmode == &Nes6502::IMP)
816 | a = temp & 0x00FF;
817 | else
818 | write(addr_abs, temp & 0x00FF);
819 | return 0;
820 | }
821 |
822 | // Instruction: Branch if Carry Clear
823 | // Function: if(C == 0) pc = address
824 | uint8_t Nes6502::BCC() {
825 | if (GetFlag(C) == 0) {
826 | cycles++;
827 | addr_abs = pc + addr_rel;
828 |
829 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++;
830 |
831 | pc = addr_abs;
832 | }
833 | return 0;
834 | }
835 |
836 | // Instruction: Branch if Carry Set
837 | // Function: if(C == 1) pc = address
838 | uint8_t Nes6502::BCS() {
839 | if (GetFlag(C) == 1) {
840 | cycles++;
841 | addr_abs = pc + addr_rel;
842 |
843 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++;
844 |
845 | pc = addr_abs;
846 | }
847 | return 0;
848 | }
849 |
850 | // Instruction: Branch if Equal
851 | // Function: if(Z == 1) pc = address
852 | uint8_t Nes6502::BEQ() {
853 | if (GetFlag(Z) == 1) {
854 | cycles++;
855 | addr_abs = pc + addr_rel;
856 |
857 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++;
858 |
859 | pc = addr_abs;
860 | }
861 | return 0;
862 | }
863 |
864 | uint8_t Nes6502::BIT() {
865 | fetch();
866 | temp = a & fetched;
867 | SetFlag(Z, (temp & 0x00FF) == 0x00);
868 | SetFlag(N, fetched & (1 << 7));
869 | SetFlag(V, fetched & (1 << 6));
870 | return 0;
871 | }
872 |
873 | // Instruction: Branch if Negative
874 | // Function: if(N == 1) pc = address
875 | uint8_t Nes6502::BMI() {
876 | if (GetFlag(N) == 1) {
877 | cycles++;
878 | addr_abs = pc + addr_rel;
879 |
880 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++;
881 |
882 | pc = addr_abs;
883 | }
884 | return 0;
885 | }
886 |
887 | // Instruction: Branch if Not Equal
888 | // Function: if(Z == 0) pc = address
889 | uint8_t Nes6502::BNE() {
890 | if (GetFlag(Z) == 0) {
891 | cycles++;
892 | addr_abs = pc + addr_rel;
893 |
894 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++;
895 |
896 | pc = addr_abs;
897 | }
898 | return 0;
899 | }
900 |
901 | // Instruction: Branch if Positive
902 | // Function: if(N == 0) pc = address
903 | uint8_t Nes6502::BPL() {
904 | if (GetFlag(N) == 0) {
905 | cycles++;
906 | addr_abs = pc + addr_rel;
907 |
908 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++;
909 |
910 | pc = addr_abs;
911 | }
912 | return 0;
913 | }
914 |
915 | // Instruction: Break
916 | // Function: Program Sourced Interrupt
917 | uint8_t Nes6502::BRK() {
918 | pc++;
919 |
920 | SetFlag(I, 1);
921 | write(0x0100 + stkp, (pc >> 8) & 0x00FF);
922 | stkp--;
923 | write(0x0100 + stkp, pc & 0x00FF);
924 | stkp--;
925 |
926 | SetFlag(B, 1);
927 | write(0x0100 + stkp, status);
928 | stkp--;
929 | SetFlag(B, 0);
930 |
931 | pc = (uint16_t)read(0xFFFE) | ((uint16_t)read(0xFFFF) << 8);
932 | return 0;
933 | }
934 |
935 | // Instruction: Branch if Overflow Clear
936 | // Function: if(V == 0) pc = address
937 | uint8_t Nes6502::BVC() {
938 | if (GetFlag(V) == 0) {
939 | cycles++;
940 | addr_abs = pc + addr_rel;
941 |
942 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++;
943 |
944 | pc = addr_abs;
945 | }
946 | return 0;
947 | }
948 |
949 | // Instruction: Branch if Overflow Set
950 | // Function: if(V == 1) pc = address
951 | uint8_t Nes6502::BVS() {
952 | if (GetFlag(V) == 1) {
953 | cycles++;
954 | addr_abs = pc + addr_rel;
955 |
956 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++;
957 |
958 | pc = addr_abs;
959 | }
960 | return 0;
961 | }
962 |
963 | // Instruction: Clear Carry Flag
964 | // Function: C = 0
965 | uint8_t Nes6502::CLC() {
966 | SetFlag(C, false);
967 | return 0;
968 | }
969 |
970 | // Instruction: Clear Decimal Flag
971 | // Function: D = 0
972 | uint8_t Nes6502::CLD() {
973 | SetFlag(D, false);
974 | return 0;
975 | }
976 |
977 | // Instruction: Disable Interrupts / Clear Interrupt Flag
978 | // Function: I = 0
979 | uint8_t Nes6502::CLI() {
980 | SetFlag(I, false);
981 | return 0;
982 | }
983 |
984 | // Instruction: Clear Overflow Flag
985 | // Function: V = 0
986 | uint8_t Nes6502::CLV() {
987 | SetFlag(V, false);
988 | return 0;
989 | }
990 |
991 | // Instruction: Compare Accumulator
992 | // Function: C <- A >= M Z <- (A - M) == 0
993 | // Flags Out: N, C, Z
994 | uint8_t Nes6502::CMP() {
995 | fetch();
996 | temp = (uint16_t)a - (uint16_t)fetched;
997 | SetFlag(C, a >= fetched);
998 | SetFlag(Z, (temp & 0x00FF) == 0x0000);
999 | SetFlag(N, temp & 0x0080);
1000 | return 1;
1001 | }
1002 |
1003 | // Instruction: Compare X Register
1004 | // Function: C <- X >= M Z <- (X - M) == 0
1005 | // Flags Out: N, C, Z
1006 | uint8_t Nes6502::CPX() {
1007 | fetch();
1008 | temp = (uint16_t)x - (uint16_t)fetched;
1009 | SetFlag(C, x >= fetched);
1010 | SetFlag(Z, (temp & 0x00FF) == 0x0000);
1011 | SetFlag(N, temp & 0x0080);
1012 | return 0;
1013 | }
1014 |
1015 | // Instruction: Compare Y Register
1016 | // Function: C <- Y >= M Z <- (Y - M) == 0
1017 | // Flags Out: N, C, Z
1018 | uint8_t Nes6502::CPY() {
1019 | fetch();
1020 | temp = (uint16_t)y - (uint16_t)fetched;
1021 | SetFlag(C, y >= fetched);
1022 | SetFlag(Z, (temp & 0x00FF) == 0x0000);
1023 | SetFlag(N, temp & 0x0080);
1024 | return 0;
1025 | }
1026 |
1027 | // Instruction: Decrement Value at Memory Location
1028 | // Function: M = M - 1
1029 | // Flags Out: N, Z
1030 | uint8_t Nes6502::DEC() {
1031 | fetch();
1032 | temp = fetched - 1;
1033 | write(addr_abs, temp & 0x00FF);
1034 | SetFlag(Z, (temp & 0x00FF) == 0x0000);
1035 | SetFlag(N, temp & 0x0080);
1036 | return 0;
1037 | }
1038 |
1039 | // Instruction: Decrement X Register
1040 | // Function: X = X - 1
1041 | // Flags Out: N, Z
1042 | uint8_t Nes6502::DEX() {
1043 | x--;
1044 | SetFlag(Z, x == 0x00);
1045 | SetFlag(N, x & 0x80);
1046 | return 0;
1047 | }
1048 |
1049 | // Instruction: Decrement Y Register
1050 | // Function: Y = Y - 1
1051 | // Flags Out: N, Z
1052 | uint8_t Nes6502::DEY() {
1053 | y--;
1054 | SetFlag(Z, y == 0x00);
1055 | SetFlag(N, y & 0x80);
1056 | return 0;
1057 | }
1058 |
1059 | // Instruction: Bitwise Logic XOR
1060 | // Function: A = A xor M
1061 | // Flags Out: N, Z
1062 | uint8_t Nes6502::EOR() {
1063 | fetch();
1064 | a = a ^ fetched;
1065 | SetFlag(Z, a == 0x00);
1066 | SetFlag(N, a & 0x80);
1067 | return 1;
1068 | }
1069 |
1070 | // Instruction: Increment Value at Memory Location
1071 | // Function: M = M + 1
1072 | // Flags Out: N, Z
1073 | uint8_t Nes6502::INC() {
1074 | fetch();
1075 | temp = fetched + 1;
1076 | write(addr_abs, temp & 0x00FF);
1077 | SetFlag(Z, (temp & 0x00FF) == 0x0000);
1078 | SetFlag(N, temp & 0x0080);
1079 | return 0;
1080 | }
1081 |
1082 | // Instruction: Increment X Register
1083 | // Function: X = X + 1
1084 | // Flags Out: N, Z
1085 | uint8_t Nes6502::INX() {
1086 | x++;
1087 | SetFlag(Z, x == 0x00);
1088 | SetFlag(N, x & 0x80);
1089 | return 0;
1090 | }
1091 |
1092 | // Instruction: Increment Y Register
1093 | // Function: Y = Y + 1
1094 | // Flags Out: N, Z
1095 | uint8_t Nes6502::INY() {
1096 | y++;
1097 | SetFlag(Z, y == 0x00);
1098 | SetFlag(N, y & 0x80);
1099 | return 0;
1100 | }
1101 |
1102 | // Instruction: Jump To Location
1103 | // Function: pc = address
1104 | uint8_t Nes6502::JMP() {
1105 | pc = addr_abs;
1106 | return 0;
1107 | }
1108 |
1109 | // Instruction: Jump To Sub-Routine
1110 | // Function: Push current pc to stack, pc = address
1111 | uint8_t Nes6502::JSR() {
1112 | pc--;
1113 |
1114 | write(0x0100 + stkp, (pc >> 8) & 0x00FF);
1115 | stkp--;
1116 | write(0x0100 + stkp, pc & 0x00FF);
1117 | stkp--;
1118 |
1119 | pc = addr_abs;
1120 | return 0;
1121 | }
1122 |
1123 | // Instruction: Load The Accumulator
1124 | // Function: A = M
1125 | // Flags Out: N, Z
1126 | uint8_t Nes6502::LDA() {
1127 | fetch();
1128 | a = fetched;
1129 | SetFlag(Z, a == 0x00);
1130 | SetFlag(N, a & 0x80);
1131 | return 1;
1132 | }
1133 |
1134 | // Instruction: Load The X Register
1135 | // Function: X = M
1136 | // Flags Out: N, Z
1137 | uint8_t Nes6502::LDX() {
1138 | fetch();
1139 | x = fetched;
1140 | SetFlag(Z, x == 0x00);
1141 | SetFlag(N, x & 0x80);
1142 | return 1;
1143 | }
1144 |
1145 | // Instruction: Load The Y Register
1146 | // Function: Y = M
1147 | // Flags Out: N, Z
1148 | uint8_t Nes6502::LDY() {
1149 | fetch();
1150 | y = fetched;
1151 | SetFlag(Z, y == 0x00);
1152 | SetFlag(N, y & 0x80);
1153 | return 1;
1154 | }
1155 |
1156 | uint8_t Nes6502::LSR() {
1157 | fetch();
1158 | SetFlag(C, fetched & 0x0001);
1159 | temp = fetched >> 1;
1160 | SetFlag(Z, (temp & 0x00FF) == 0x0000);
1161 | SetFlag(N, temp & 0x0080);
1162 | if (lookup[opcode].addrmode == &Nes6502::IMP)
1163 | a = temp & 0x00FF;
1164 | else
1165 | write(addr_abs, temp & 0x00FF);
1166 | return 0;
1167 | }
1168 |
1169 | uint8_t Nes6502::NOP() {
1170 | switch (opcode) {
1171 | case 0x1C:
1172 | case 0x3C:
1173 | case 0x5C:
1174 | case 0x7C:
1175 | case 0xDC:
1176 | case 0xFC:
1177 | return 1;
1178 | break;
1179 | }
1180 | return 0;
1181 | }
1182 |
1183 | // Instruction: Bitwise Logic OR
1184 | // Function: A = A | M
1185 | // Flags Out: N, Z
1186 | uint8_t Nes6502::ORA() {
1187 | fetch();
1188 | a = a | fetched;
1189 | SetFlag(Z, a == 0x00);
1190 | SetFlag(N, a & 0x80);
1191 | return 1;
1192 | }
1193 |
1194 | // Instruction: Push Accumulator to Stack
1195 | // Function: A -> stack
1196 | uint8_t Nes6502::PHA() {
1197 | write(0x0100 + stkp, a);
1198 | stkp--;
1199 | return 0;
1200 | }
1201 |
1202 | // Instruction: Push Status Register to Stack
1203 | // Function: status -> stack
1204 | // Note: Break flag is set to 1 before push
1205 | uint8_t Nes6502::PHP() {
1206 | write(0x0100 + stkp, status | B | U);
1207 | SetFlag(B, 0);
1208 | SetFlag(U, 0);
1209 | stkp--;
1210 | return 0;
1211 | }
1212 |
1213 | // Instruction: Pop Accumulator off Stack
1214 | // Function: A <- stack
1215 | // Flags Out: N, Z
1216 | uint8_t Nes6502::PLA() {
1217 | stkp++;
1218 | a = read(0x0100 + stkp);
1219 | SetFlag(Z, a == 0x00);
1220 | SetFlag(N, a & 0x80);
1221 | return 0;
1222 | }
1223 |
1224 | // Instruction: Pop Status Register off Stack
1225 | // Function: Status <- stack
1226 | uint8_t Nes6502::PLP() {
1227 | stkp++;
1228 | status = read(0x0100 + stkp);
1229 | SetFlag(U, 1);
1230 | return 0;
1231 | }
1232 |
1233 | uint8_t Nes6502::ROL() {
1234 | fetch();
1235 | temp = (uint16_t)(fetched << 1) | GetFlag(C);
1236 | SetFlag(C, temp & 0xFF00);
1237 | SetFlag(Z, (temp & 0x00FF) == 0x0000);
1238 | SetFlag(N, temp & 0x0080);
1239 | if (lookup[opcode].addrmode == &Nes6502::IMP)
1240 | a = temp & 0x00FF;
1241 | else
1242 | write(addr_abs, temp & 0x00FF);
1243 | return 0;
1244 | }
1245 |
1246 | uint8_t Nes6502::ROR() {
1247 | fetch();
1248 | temp = (uint16_t)(GetFlag(C) << 7) | (fetched >> 1);
1249 | SetFlag(C, fetched & 0x01);
1250 | SetFlag(Z, (temp & 0x00FF) == 0x00);
1251 | SetFlag(N, temp & 0x0080);
1252 | if (lookup[opcode].addrmode == &Nes6502::IMP)
1253 | a = temp & 0x00FF;
1254 | else
1255 | write(addr_abs, temp & 0x00FF);
1256 | return 0;
1257 | }
1258 |
1259 | uint8_t Nes6502::RTI() {
1260 | stkp++;
1261 | status = read(0x0100 + stkp);
1262 | status &= ~B;
1263 | status &= ~U;
1264 |
1265 | stkp++;
1266 | pc = (uint16_t)read(0x0100 + stkp);
1267 | stkp++;
1268 | pc |= (uint16_t)read(0x0100 + stkp) << 8;
1269 | return 0;
1270 | }
1271 |
1272 | uint8_t Nes6502::RTS() {
1273 | stkp++;
1274 | pc = (uint16_t)read(0x0100 + stkp);
1275 | stkp++;
1276 | pc |= (uint16_t)read(0x0100 + stkp) << 8;
1277 |
1278 | pc++;
1279 | return 0;
1280 | }
1281 |
1282 | // Instruction: Set Carry Flag
1283 | // Function: C = 1
1284 | uint8_t Nes6502::SEC() {
1285 | SetFlag(C, true);
1286 | return 0;
1287 | }
1288 |
1289 | // Instruction: Set Decimal Flag
1290 | // Function: D = 1
1291 | uint8_t Nes6502::SED() {
1292 | SetFlag(D, true);
1293 | return 0;
1294 | }
1295 |
1296 | // Instruction: Set Interrupt Flag / Enable Interrupts
1297 | // Function: I = 1
1298 | uint8_t Nes6502::SEI() {
1299 | SetFlag(I, true);
1300 | return 0;
1301 | }
1302 |
1303 | // Instruction: Store Accumulator at Address
1304 | // Function: M = A
1305 | uint8_t Nes6502::STA() {
1306 | write(addr_abs, a);
1307 | return 0;
1308 | }
1309 |
1310 | // Instruction: Store X Register at Address
1311 | // Function: M = X
1312 | uint8_t Nes6502::STX() {
1313 | write(addr_abs, x);
1314 | return 0;
1315 | }
1316 |
1317 | // Instruction: Store Y Register at Address
1318 | // Function: M = Y
1319 | uint8_t Nes6502::STY() {
1320 | write(addr_abs, y);
1321 | return 0;
1322 | }
1323 |
1324 | // Instruction: Transfer Accumulator to X Register
1325 | // Function: X = A
1326 | // Flags Out: N, Z
1327 | uint8_t Nes6502::TAX() {
1328 | x = a;
1329 | SetFlag(Z, x == 0x00);
1330 | SetFlag(N, x & 0x80);
1331 | return 0;
1332 | }
1333 |
1334 | // Instruction: Transfer Accumulator to Y Register
1335 | // Function: Y = A
1336 | // Flags Out: N, Z
1337 | uint8_t Nes6502::TAY() {
1338 | y = a;
1339 | SetFlag(Z, y == 0x00);
1340 | SetFlag(N, y & 0x80);
1341 | return 0;
1342 | }
1343 |
1344 | // Instruction: Transfer Stack Pointer to X Register
1345 | // Function: X = stack pointer
1346 | // Flags Out: N, Z
1347 | uint8_t Nes6502::TSX() {
1348 | x = stkp;
1349 | SetFlag(Z, x == 0x00);
1350 | SetFlag(N, x & 0x80);
1351 | return 0;
1352 | }
1353 |
1354 | // Instruction: Transfer X Register to Accumulator
1355 | // Function: A = X
1356 | // Flags Out: N, Z
1357 | uint8_t Nes6502::TXA() {
1358 | a = x;
1359 | SetFlag(Z, a == 0x00);
1360 | SetFlag(N, a & 0x80);
1361 | return 0;
1362 | }
1363 |
1364 | // Instruction: Transfer X Register to Stack Pointer
1365 | // Function: stack pointer = X
1366 | uint8_t Nes6502::TXS() {
1367 | stkp = x;
1368 | return 0;
1369 | }
1370 |
1371 | // Instruction: Transfer Y Register to Accumulator
1372 | // Function: A = Y
1373 | // Flags Out: N, Z
1374 | uint8_t Nes6502::TYA() {
1375 | a = y;
1376 | SetFlag(Z, a == 0x00);
1377 | SetFlag(N, a & 0x80);
1378 | return 0;
1379 | }
1380 |
1381 | // This function captures illegal opcodes
1382 | uint8_t Nes6502::XXX() { return 0; }
1383 |
1384 | ///////////////////////////////////////////////////////////////////////////////
1385 | // HELPER FUNCTIONS
1386 |
1387 | bool Nes6502::complete() { return cycles == 0; }
1388 |
1389 | // This is the disassembly function. Its workings are not required for
1390 | // emulation. It is merely a convenience function to turn the binary instruction
1391 | // code into human readable form. Its included as part of the emulator because
1392 | // it can take advantage of many of the CPUs internal operations to do this.
1393 | std::map Nes6502::disassemble(uint16_t nStart,
1394 | uint16_t nStop) {
1395 | uint32_t addr = nStart;
1396 | uint8_t value = 0x00, lo = 0x00, hi = 0x00;
1397 | std::map mapLines;
1398 | uint16_t line_addr = 0;
1399 |
1400 | // A convenient utility to convert variables into
1401 | // hex strings because "modern C++"'s method with
1402 | // streams is atrocious
1403 | auto hex = [](uint32_t n, uint8_t d) {
1404 | std::string s(d, '0');
1405 | for (int i = d - 1; i >= 0; i--, n >>= 4)
1406 | s[i] = "0123456789ABCDEF"[n & 0xF];
1407 | return s;
1408 | };
1409 |
1410 | // Starting at the specified address we read an instruction
1411 | // byte, which in turn yields information from the lookup table
1412 | // as to how many additional bytes we need to read and what the
1413 | // addressing mode is. I need this info to assemble human readable
1414 | // syntax, which is different depending upon the addressing mode
1415 |
1416 | // As the instruction is decoded, a std::string is assembled
1417 | // with the readable output
1418 | while (addr <= (uint32_t)nStop) {
1419 | line_addr = addr;
1420 |
1421 | // Prefix line with instruction address
1422 | std::string sInst = "$" + hex(addr, 4) + ": ";
1423 |
1424 | // Read instruction, and get its readable name
1425 | uint8_t opcode = bus->cpuRead(addr, true);
1426 | addr++;
1427 | sInst += lookup[opcode].name + " ";
1428 |
1429 | // Get oprands from desired locations, and form the
1430 | // instruction based upon its addressing mode. These
1431 | // routines mimmick the actual fetch routine of the
1432 | // 6502 in order to get accurate data as part of the
1433 | // instruction
1434 | if (lookup[opcode].addrmode == &Nes6502::IMP) {
1435 | sInst += " {IMP}";
1436 | } else if (lookup[opcode].addrmode == &Nes6502::IMM) {
1437 | value = bus->cpuRead(addr, true);
1438 | addr++;
1439 | sInst += "#$" + hex(value, 2) + " {IMM}";
1440 | } else if (lookup[opcode].addrmode == &Nes6502::ZP0) {
1441 | lo = bus->cpuRead(addr, true);
1442 | addr++;
1443 | hi = 0x00;
1444 | sInst += "$" + hex(lo, 2) + " {ZP0}";
1445 | } else if (lookup[opcode].addrmode == &Nes6502::ZPX) {
1446 | lo = bus->cpuRead(addr, true);
1447 | addr++;
1448 | hi = 0x00;
1449 | sInst += "$" + hex(lo, 2) + ", X {ZPX}";
1450 | } else if (lookup[opcode].addrmode == &Nes6502::ZPY) {
1451 | lo = bus->cpuRead(addr, true);
1452 | addr++;
1453 | hi = 0x00;
1454 | sInst += "$" + hex(lo, 2) + ", Y {ZPY}";
1455 | } else if (lookup[opcode].addrmode == &Nes6502::IZX) {
1456 | lo = bus->cpuRead(addr, true);
1457 | addr++;
1458 | hi = 0x00;
1459 | sInst += "($" + hex(lo, 2) + ", X) {IZX}";
1460 | } else if (lookup[opcode].addrmode == &Nes6502::IZY) {
1461 | lo = bus->cpuRead(addr, true);
1462 | addr++;
1463 | hi = 0x00;
1464 | sInst += "($" + hex(lo, 2) + "), Y {IZY}";
1465 | } else if (lookup[opcode].addrmode == &Nes6502::ABS) {
1466 | lo = bus->cpuRead(addr, true);
1467 | addr++;
1468 | hi = bus->cpuRead(addr, true);
1469 | addr++;
1470 | sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + " {ABS}";
1471 | } else if (lookup[opcode].addrmode == &Nes6502::ABX) {
1472 | lo = bus->cpuRead(addr, true);
1473 | addr++;
1474 | hi = bus->cpuRead(addr, true);
1475 | addr++;
1476 | sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + ", X {ABX}";
1477 | } else if (lookup[opcode].addrmode == &Nes6502::ABY) {
1478 | lo = bus->cpuRead(addr, true);
1479 | addr++;
1480 | hi = bus->cpuRead(addr, true);
1481 | addr++;
1482 | sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + ", Y {ABY}";
1483 | } else if (lookup[opcode].addrmode == &Nes6502::IND) {
1484 | lo = bus->cpuRead(addr, true);
1485 | addr++;
1486 | hi = bus->cpuRead(addr, true);
1487 | addr++;
1488 | sInst += "($" + hex((uint16_t)(hi << 8) | lo, 4) + ") {IND}";
1489 | } else if (lookup[opcode].addrmode == &Nes6502::REL) {
1490 | value = bus->cpuRead(addr, true);
1491 | addr++;
1492 | sInst += "$" + hex(value, 2) + " [$" + hex(addr + (int8_t)value, 4) +
1493 | "] {REL}";
1494 | }
1495 |
1496 | // Add the formed string to a std::map, using the instruction's
1497 | // address as the key. This makes it convenient to look for later
1498 | // as the instructions are variable in length, so a straight up
1499 | // incremental index is not sufficient.
1500 | mapLines[line_addr] = sInst;
1501 | }
1502 |
1503 | return mapLines;
1504 | }
1505 |
1506 | // End of File - Jx9
1507 |
--------------------------------------------------------------------------------
/6502/6502Lib/src/public/Nes6502.h:
--------------------------------------------------------------------------------
1 | /*
2 | Nes6502 - An emulation of the 6502/2A03 processor
3 | "Thanks Dad for believing computers were gonna be a big deal..." -
4 | javidx9
5 |
6 | License (OLC-3)
7 | ~~~~~~~~~~~~~~~
8 |
9 | Copyright 2018-2019 OneLoneCoder.com
10 |
11 | Redistribution and use in source and binary forms, with or without
12 | modification, are permitted provided that the following conditions
13 | are met:
14 |
15 | 1. Redistributions or derivations of source code must retain the above
16 | copyright notice, this list of conditions and the following disclaimer.
17 |
18 | 2. Redistributions or derivative works in binary form must reproduce
19 | the above copyright notice. This list of conditions and the following
20 | disclaimer must be reproduced in the documentation and/or other
21 | materials provided with the distribution.
22 |
23 | 3. Neither the name of the copyright holder nor the names of its
24 | contributors may be used to endorse or promote products derived
25 | from this software without specific prior written permission.
26 |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 |
39 | Background
40 | ~~~~~~~~~~
41 | I love this microprocessor. It was at the heart of two of my favourite
42 | machines, the BBC Micro, and the Nintendo Entertainment System, as well
43 | as countless others in that era. I learnt to program on the Model B, and
44 | I learnt to love games on the NES, so in many ways, this processor is
45 | why I am the way I am today.
46 |
47 | In February 2019, I decided to undertake a selfish personal project and
48 | build a NES emulator. Ive always wanted to, and as such I've avoided
49 | looking at source code for such things. This made making this a real
50 | personal challenge. I know its been done countless times, and very
51 | likely in far more clever and accurate ways than mine, but I'm proud of this.
52 |
53 | Datasheet: http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
54 |
55 | Files: Nes6502.h, Nes6502.cpp
56 |
57 | Relevant Video: https://youtu.be/8XmxKPJDGU0
58 |
59 | Links
60 | ~~~~~
61 | YouTube: https://www.youtube.com/javidx9
62 | https://www.youtube.com/javidx9extra
63 | Discord: https://discord.gg/WhwHUMV
64 | Twitter: https://www.twitter.com/javidx9
65 | Twitch: https://www.twitch.tv/javidx9
66 | GitHub: https://www.github.com/onelonecoder
67 | Patreon: https://www.patreon.com/javidx9
68 | Homepage: https://www.onelonecoder.com
69 |
70 | Author
71 | ~~~~~~
72 | David Barr, aka javidx9, �OneLoneCoder 2019
73 | */
74 |
75 | #pragma once
76 |
77 | // With little modification, reliance upon the stdlib can
78 | // be removed entirely if required.
79 |
80 | // Ths is required for translation table and disassembler. The table
81 | // could be implemented straight up as an array, but I used a vector.
82 | #include
83 |
84 | // These are required for disassembler. If you dont require disassembly
85 | // then just remove the function.
86 | #include