OXYGENHUB
◆ main enhanced-x16-emulator / src / cpu / fake6502.c
11.4 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
/* Fake6502 CPU emulator core v1.1 *******************
2
 * (c)2011 Mike Chambers ([email protected])       *
3
 *****************************************************
4
 * v1.1 - Small bugfix in BIT opcode, but it was the *
5
 *        difference between a few games in my NES   *
6
 *        emulator working and being broken!         *
7
 *        I went through the rest carefully again    *
8
 *        after fixing it just to make sure I didn't *
9
 *        have any other typos! (Dec. 17, 2011)      *
10
 *                                                   *
11
 * v1.0 - First release (Nov. 24, 2011)              *
12
 *****************************************************
13
 * LICENSE: This source code is released into the    *
14
 * public domain, but if you use it please do give   *
15
 * credit. I put a lot of effort into writing this!  *
16
 *                                                   *
17
 *****************************************************
18
 * Fake6502 is a MOS Technology 6502 CPU emulation   *
19
 * engine in C. It was written as part of a Nintendo *
20
 * Entertainment System emulator I've been writing.  *
21
 *                                                   *
22
 * If you do discover an error in timing accuracy,   *
23
 * or operation in general please e-mail me at the   *
24
 * address above so that I can fix it. Thank you!    *
25
 *                                                   *
26
 *****************************************************
27
 * Usage:                                            *
28
 *                                                   *
29
 * Fake6502 requires you to provide two external     *
30
 * functions:                                        *
31
 *                                                   *
32
 * uint8_t read6502(uint16_t address, uint8_t bank)  *
33
 * void write6502(uint16_t address, uint8_t bank,    *
34
 * uint8_t value)                                    *
35
 *                                                   *
36
 * You may optionally pass Fake6502 the pointer to a *
37
 * function which you want to be called after every  *
38
 * emulated instruction. This function should be a   *
39
 * void with no parameters expected to be passed to  *
40
 * it.                                               *
41
 *                                                   *
42
 * This can be very useful. For example, in a NES    *
43
 * emulator, you check the number of clock ticks     *
44
 * that have passed so you can know when to handle   *
45
 * APU events.                                       *
46
 *                                                   *
47
 * To pass Fake6502 this pointer, use the            *
48
 * hookexternal(void *funcptr) function provided.    *
49
 *                                                   *
50
 * To disable the hook later, pass NULL to it.       *
51
 *****************************************************
52
 * Useful functions in this emulator:                *
53
 *                                                   *
54
 * void reset6502(bool c816)                         *
55
 *   - Call this once before you begin execution.    *
56
 *                                                   *
57
 * void exec6502(uint32_t tickcount)                 *
58
 *   - Execute 6502 code up to the next specified    *
59
 *     count of clock ticks.                         *
60
 *                                                   *
61
 * void step6502()                                   *
62
 *   - Execute a single instrution.                  *
63
 *                                                   *
64
 * void irq6502()                                    *
65
 *   - Trigger a hardware IRQ in the 6502 core.      *
66
 *                                                   *
67
 * void nmi6502()                                    *
68
 *   - Trigger an NMI in the 6502 core.              *
69
 *                                                   *
70
 * void hookexternal(void *funcptr)                  *
71
 *   - Pass a pointer to a void function taking no   *
72
 *     parameters. This will cause Fake6502 to call  *
73
 *     that function once after each emulated        *
74
 *     instruction.                                  *
75
 *                                                   *
76
 *****************************************************
77
 * Useful variables in this emulator:                *
78
 *                                                   *
79
 * uint32_t clockticks6502                           *
80
 *   - A running total of the emulated cycle count.  *
81
 *                                                   *
82
 * uint32_t instructions                             *
83
 *   - A running total of the total emulated         *
84
 *     instruction count. This is not related to     *
85
 *     clock cycle timing.                           *
86
 *                                                   *
87
 *****************************************************/
88
89
#include "fake6502.h"
90
#include "registers.h"
91
#include "irq_ctx.h"
92
93
#include <stdio.h>
94
#include <stdint.h>
95
96
// getvalue16() below is currently unused but kept for completeness. GCC and
97
// Clang warn about unused statics under -Wall -Werror; MSVC has no equivalent
98
// attribute and rejects __attribute__ outright.
99
#ifdef _MSC_VER
100
#define MAYBE_UNUSED
101
#else
102
#define MAYBE_UNUSED __attribute__((unused))
103
#endif
104
105
// 6502 / 65C816 registers
106
107
struct regs regs;
108
109
//helper variables
110
uint32_t instructions = 0; //keep track of total instructions executed
111
uint32_t clockticks6502 = 0, clockgoal6502 = 0;
112
uint16_t opcode_addr, oldpc, reladdr, value;
113
uint32_t ea;
114
uint32_t result;
115
uint8_t opcode, oldstatus;
116
117
bool warn_rockwell = true;
118
119
uint8_t penaltyop, penaltyaddr;
120
uint8_t penaltym = 0;
121
uint8_t penaltye = 0;
122
uint8_t penaltyn = 0;
123
uint8_t penaltyx = 0;
124
uint8_t penaltyd = 0;
125
uint8_t waiting = 0;
126
127
// Set by acc() and cleared before each instruction, because the obvious test --
128
// comparing addrtable[opcode] against acc -- is not reliable. acc() and imp()
129
// are both empty, and MSVC folds identical functions onto one address, so the
130
// comparison is true for every implied opcode there and false under GCC.
131
static bool addressing_is_acc = false;
132
133
//externally supplied functions
134
extern uint8_t read6502(uint16_t address, uint8_t bank);
135
extern void write6502(uint16_t address, uint8_t bank, uint8_t value);
136
extern void stop6502(uint16_t address, uint8_t bank);
137
extern void vp6502();
138
extern uint8_t memory_get_ram_bank();
139
extern uint8_t memory_get_rom_bank();
140
141
static void (*addrtable_c02[256])();
142
static void (*addrtable_c816[256])();
143
static void (*optable_c02[256])();
144
static void (*optable_c816[256])();
145
146
static const uint32_t ticktable_c02[256];
147
static const uint32_t ticktable_c816[256];
148
149
static void (**addrtable)();
150
static void (**optable)();
151
static const uint32_t *ticktable;
152
153
#include "support.h"
154
#include "modes.h"
155
156
void rockwell_warning(const char *instruction) {
157
    uint8_t pc_bank;
158
159
    if (opcode_addr < 0xa000) {
160
        pc_bank = 0;
161
    } else if (opcode_addr < 0xc000) {
162
        pc_bank = memory_get_ram_bank();
163
    } else {
164
        pc_bank = memory_get_rom_bank();
165
    }
166
167
    printf("Warning: encountered Rockwell instruction %s at $%02x:%04x.\n", instruction, pc_bank, opcode_addr);
168
    printf("\tFuture Commander X16 hardware may ship with a 65C816 CPU,\n");
169
    printf("\twhich does not support these instructions.\n");
170
    printf("\tThis will be the only warning given for Rockwell\n");
171
    printf("\tinstructions until the emulator is relaunched.\n");
172
    printf("\tPass -rockwell to the command line to suppress this warning.\n\n");
173
174
    warn_rockwell = false;
175
}
176
177
static uint8_t wrappedBankByte(uint32_t addr) {
178
    // The direct page and the stack are relocatable anywhere inside bank zero,
179
    // by D and by S, but neither can leave it. So the second byte of a 16-bit
180
    // access wraps within bank zero rather than stepping into bank one.
181
    if (addrtable[opcode] == zp || addrtable[opcode] == zpx ||
182
        addrtable[opcode] == zpy || addrtable[opcode] == sr) {
183
        return 0;
184
    }
185
    else {
186
        return bank_byte(addr+1);
187
    }
188
}
189
190
static uint16_t getvalue(bool use16Bit) {
191
    if (addressing_is_acc) {
192
        return use16Bit ? regs.c : (uint16_t)regs.a;
193
    } else if (use16Bit) {
194
        return ((uint16_t)read6502(ea, bank_byte(ea)) | ((uint16_t)read6502(ea+1, wrappedBankByte(ea)) << 8));
195
    }
196
    return read6502(ea, bank_byte(ea));
197
}
198
199
static uint16_t MAYBE_UNUSED getvalue16() {
200
    return((uint16_t)read6502(ea, bank_byte(ea)) | ((uint16_t)read6502(ea+1, wrappedBankByte(ea)) << 8));
201
}
202
203
static void putvalue(uint16_t saveval, bool use16Bit) {
204
    if (addressing_is_acc) {
205
        if (use16Bit) {
206
            regs.c = saveval;
207
        } else {
208
            regs.a = (uint8_t)(saveval & 0x00FF);
209
        }
210
    } else if (use16Bit) {
211
        write6502(ea, bank_byte(ea), (saveval & 0x00FF));
212
        write6502(ea + 1, wrappedBankByte(ea), saveval >> 8);
213
    } else {
214
        write6502(ea, bank_byte(ea), (saveval & 0x00FF));
215
    }
216
}
217
218
#include "instructions.h"
219
#include "65c02.h"
220
#include "tables.h"
221
222
void nmi6502() {
223
    interrupt6502(INT_NMI);
224
    waiting = 0;
225
}
226
227
void irq6502() {
228
    if (!(regs.status & FLAG_INTERRUPT)) {
229
        interrupt6502(INT_IRQ);
230
    }
231
    waiting = 0;
232
}
233
234
uint8_t callexternal = 0;
235
void (*loopexternal)();
236
237
void exec6502(uint32_t tickcount) {
238
	if (waiting) {
239
		clockticks6502 += tickcount;
240
		clockgoal6502 = clockticks6502;
241
		return;
242
    }
243
244
    opcode_addr = regs.pc;
245
246
    clockgoal6502 += tickcount;
247
248
    while (clockticks6502 < clockgoal6502) {
249
        opcode = read6502(regs.pc++, regs.k);
250
251
        cpu_pin_status_flags();
252
253
        penaltyop = 0;
254
        penaltyaddr = 0;
255
        penaltym = 0;
256
        penaltye = 0;
257
        penaltyn = 0;
258
        penaltyx = 0;
259
        penaltyd = 0;
260
        addressing_is_acc = false;
261
262
        (*addrtable[opcode])();
263
        (*optable[opcode])();
264
        cpu_pin_stack_page();
265
        clockticks6502 += ticktable[opcode];
266
267
        // Keep this in sync with step6502().
268
        if (penaltyop && penaltyaddr) clockticks6502++;
269
        if (memory_16bit()) clockticks6502 += penaltym;
270
        if (index_16bit()) clockticks6502 += penaltyx;
271
        if (penaltyn && !regs.e) clockticks6502++;
272
        if (penaltye && regs.e) clockticks6502++;
273
        if (penaltyd) clockticks6502++;
274
275
        instructions++;
276
277
        if (callexternal) (*loopexternal)();
278
    }
279
}
280
281
void step6502() {
282
	if (waiting) {
283
		++clockticks6502;
284
		clockgoal6502 = clockticks6502;
285
		return;
286
	}
287
288
    opcode_addr = regs.pc;
289
290
    opcode = read6502(regs.pc++, regs.k);
291
292
    cpu_pin_status_flags();
293
294
    penaltyop = 0;
295
    penaltyaddr = 0;
296
    penaltym = 0;
297
    penaltye = 0;
298
    penaltyn = 0;
299
    penaltyx = 0;
300
    // Otherwise this sticks: after one misaligned direct-page access every later
301
    // instruction costs an extra cycle, and reset6502() doesn't clear it either.
302
    penaltyd = 0;
303
        addressing_is_acc = false;
304
305
    (*addrtable[opcode])();
306
    (*optable[opcode])();
307
    cpu_pin_stack_page();
308
    clockticks6502 += ticktable[opcode];
309
310
    if (penaltyop && penaltyaddr) clockticks6502++;
311
    if (memory_16bit()) clockticks6502 += penaltym;
312
    if (index_16bit()) clockticks6502 += penaltyx;
313
    if (penaltyn && !regs.e) clockticks6502++;
314
    if (penaltye && regs.e) clockticks6502++;
315
    if (penaltyd) clockticks6502 ++;
316
    clockgoal6502 = clockticks6502;
317
318
    instructions++;
319
320
    if (callexternal) (*loopexternal)();
321
}
322
323
void hookexternal(void *funcptr) {
324
    if (funcptr != (void *)NULL) {
325
        loopexternal = funcptr;
326
        callexternal = 1;
327
    } else callexternal = 0;
328
}
329
330
//  Fixes from http://6502.org/tutorials/65c02opcodes.html
331
//
332
//  65C02 Cycle Count differences.
333
//        ADC/SBC work differently in decimal mode.
334
//        The wraparound fixes may not be required.