1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
#include <lcq/6502.h>
#include <lcq/6502/ins.h>
#include <stdlib.h>
#include <stdio.h>
static u8 basic_read_handler(void *data, u16 addr) {
return ((u8 *) data)[addr];
}
static void basic_write_handler(void *data, u16 addr, u8 val) {
((u8 *) data)[addr] = val;
}
void l6502_mem_basic_new(l6502_mem *m) {
m->rh = basic_read_handler;
m->wh = basic_write_handler;
m->data = calloc(1 << 16, sizeof(u8));
}
static void flags_check_zn(l6502_emu *e, u8 v) {
l6502_flag_set(e, L6502_FLAG_ZERO, v == 0);
l6502_flag_set(e, L6502_FLAG_NEGATIVE, (v >> 7) & 0b1);
}
static u16 amode_immediate(l6502_emu *e) {
return e->pc++;
}
static u16 amode_absolute(l6502_emu *e) {
u16 addr = l6502_read_addr(e, e->pc);
e->pc += 2;
return addr;
}
static u16 amode_absolute_x(l6502_emu *e) {
u16 addr = l6502_read_addr(e, e->pc) + (u16) e->regs[L6502_REG_X];
e->pc += 2;
return addr;
}
static u16 amode_absolute_y(l6502_emu *e) {
u16 addr = l6502_read_addr(e, e->pc) + (u16) e->regs[L6502_REG_Y];
e->pc += 2;
return addr;
}
static u16 amode_zeropage(l6502_emu *e) {
return (u16) l6502_read(e, e->pc++);
}
static u16 amode_zeropage_x(l6502_emu *e) {
return (u16) (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_X]);
}
static u16 amode_zeropage_y(l6502_emu *e) {
return (u16) (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_Y]);
}
static u16 amode_indirect_zeropage_x(l6502_emu *e) {
return l6502_read_addr_wrap_page(e, (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_X]));
}
static u16 amode_indirect_zeropage_y(l6502_emu *e) {
return l6502_read_addr_wrap_page(e, l6502_read(e, e->pc++)) + (u16) e->regs[L6502_REG_Y];
}
const char *l6502_emulate(l6502_emu *e) {
u8 ins = l6502_read(e, e->pc++);
u8 val = 0;
u16 res = 0;
bool set_flags = true;
bool set_carry = false;
bool set_result = true;
l6502_reg set_reg = L6502_REG__MARKER;
u16 addr = 0;
const char *nm = NULL;
switch (ins) { /* identify some special-case instructions */
case BRK:
nm = "brk";
l6502_push_addr(e, e->pc + 1);
l6502_push(e, e->regs[L6502_REG_FLAGS] | L6502_FLAG_BREAK_COMMAND);
e->pc = l6502_read_addr(e, 0xfffe);
l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true);
return nm;
case PHP: nm = "php"; l6502_push(e, e->regs[L6502_REG_FLAGS] | L6502_FLAG_BREAK_COMMAND | L6502_FLAG_UNUSED); return nm;
case CLC: nm = "clc"; l6502_flag_set(e, L6502_FLAG_CARRY, false); return nm;
case JSR: {
nm = "jsr";
u16 alo = l6502_read(e, e->pc);
l6502_push_addr(e, e->pc + 1);
u16 ahi = l6502_read(e, e->pc + 1);
e->pc = ahi << 8 | alo;
return nm;
}
case PLP: nm = "plp"; e->regs[L6502_REG_FLAGS] = l6502_pop(e) & (u8) ~L6502_FLAG_BREAK_COMMAND; return nm;
case SEC: nm = "sec"; l6502_flag_set(e, L6502_FLAG_CARRY, true); return nm;
case RTI:
nm = "rti";
e->regs[L6502_REG_FLAGS] = l6502_pop(e) & (u8) ~L6502_FLAG_BREAK_COMMAND;
e->pc = l6502_pop_addr(e);
return nm;
case PHA: nm = "pha"; l6502_push(e, e->regs[L6502_REG_A]); return nm;
case JMP_ABSOLUTE:
nm = "jmp";
addr = l6502_read_addr(e, e->pc);
e->pc = addr;
return nm;
case CLI: nm = "cli"; l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, false); return nm;
case RTS: nm = "rts"; e->pc = l6502_pop_addr(e) + 1; return nm;
case PLA: nm = "pla"; e->regs[L6502_REG_A] = l6502_pop(e); flags_check_zn(e, e->regs[L6502_REG_A]); return nm;
case JMP_INDIRECT:
nm = "jmp";
addr = l6502_read_addr_wrap_page(e, l6502_read_addr(e, e->pc));
e->pc = addr;
return nm;
case SEI: nm = "sei"; l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true); return nm;
case DEY: nm = "dey"; e->regs[L6502_REG_Y] -= 1; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm;
case TXA: nm = "txa"; e->regs[L6502_REG_A] = e->regs[L6502_REG_X]; flags_check_zn(e, e->regs[L6502_REG_A]); return nm;
case TYA: nm = "tya"; e->regs[L6502_REG_A] = e->regs[L6502_REG_Y]; flags_check_zn(e, e->regs[L6502_REG_A]); return nm;
case TXS: nm = "txs"; e->regs[L6502_REG_SP] = e->regs[L6502_REG_X]; return nm; /* note TXS does not set flags */
case TAY: nm = "tay"; e->regs[L6502_REG_Y] = e->regs[L6502_REG_A]; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm;
case TAX: nm = "tax"; e->regs[L6502_REG_X] = e->regs[L6502_REG_A]; flags_check_zn(e, e->regs[L6502_REG_X]); return nm;
case CLV: nm = "clv"; l6502_flag_set(e, L6502_FLAG_OVERFLOW, false); return nm;
case TSX: nm = "tsx"; e->regs[L6502_REG_X] = e->regs[L6502_REG_SP]; flags_check_zn(e, e->regs[L6502_REG_X]); return nm;
case INY: nm = "iny"; e->regs[L6502_REG_Y] += 1; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm;
case DEX: nm = "dex"; e->regs[L6502_REG_X] -= 1; flags_check_zn(e, e->regs[L6502_REG_X]); return nm;
case CLD: nm = "cld"; l6502_flag_set(e, L6502_FLAG_DECIMAL, false); return nm;
case INX: nm = "inx"; e->regs[L6502_REG_X] += 1; flags_check_zn(e, e->regs[L6502_REG_X]); return nm;
case NOP: nm = "nop"; return nm;
case SED: nm = "sed"; l6502_flag_set(e, L6502_FLAG_DECIMAL, true); return nm;
}
if ((ins & 0b11111) == 0b10000) { /* identify conditional branches */
l6502_flag flags[4] = { L6502_FLAG_NEGATIVE, L6502_FLAG_OVERFLOW, L6502_FLAG_CARRY, L6502_FLAG_ZERO };
u8 off = l6502_read(e, e->pc++);
if (l6502_flag_get(e, flags[(ins >> 6) & 0b11]) == ((ins >> 5) & 0b1)) {
e->pc = (u16) ((i16) e->pc + (i16) (i8) off);
}
return "jcc";
}
switch (ins & 0b11) { /* identify instructions in common forms */
case 0b01: { /* group one */
set_reg = L6502_REG_A;
switch ((ins >> 2) & 0b111) {
case G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_X: addr = amode_indirect_zeropage_x(e); break;
case G1_ADDRESSING_MODE_ZEROPAGE: addr = amode_zeropage(e); break;
case G1_ADDRESSING_MODE_IMMEDIATE: addr = amode_immediate(e); break;
case G1_ADDRESSING_MODE_ABSOLUTE: addr = amode_absolute(e); break;
case G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_Y: addr = amode_indirect_zeropage_y(e); break;
case G1_ADDRESSING_MODE_ZEROPAGE_X: addr = amode_zeropage_x(e); break;
case G1_ADDRESSING_MODE_ABSOLUTE_Y: addr = amode_absolute_y(e); break;
case G1_ADDRESSING_MODE_ABSOLUTE_X: addr = amode_absolute_x(e); break;
}
val = l6502_read(e, addr);
switch ((ins >> 5) & 0b111) {
case G1_OPCODE_ORA: nm = "ora"; res = (u16) e->regs[L6502_REG_A] | (u16) val; break;
case G1_OPCODE_AND: nm = "and"; res = (u16) e->regs[L6502_REG_A] & (u16) val; break;
case G1_OPCODE_EOR: nm = "eor"; res = (u16) e->regs[L6502_REG_A] ^ (u16) val; break;
case G1_OPCODE_ADC: {
nm = "adc";
set_carry = true;
i8 a0 = (i8) e->regs[L6502_REG_A];
i8 a1 = (i8) val;
i8 a2 = (i8) l6502_flag_get(e, L6502_FLAG_CARRY);
res = (u16) (u8) a0 + (u16) (u8) a1; res += (u16) (u8) a2;
l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((a0 >= 0) == (a1 >= 0)) && ((a0 >= 0) != ((i8) res >= 0)));
break;
}
case G1_OPCODE_STA: nm = "sta"; l6502_write(e, addr, e->regs[L6502_REG_A]); set_flags = set_result = false; break;
case G1_OPCODE_LDA: nm = "lda"; res = (u16) l6502_read(e, addr); break;
case G1_OPCODE_CMP:
nm = "cmp";
set_result = false;
set_carry = true;
res = (u16) (u8) (e->regs[L6502_REG_A] - val);
if (e->regs[L6502_REG_A] >= val) res |= 1 << 8;
break;
case G1_OPCODE_SBC:
nm = "sbc";
set_carry = true;
i8 a0 = (i8) e->regs[L6502_REG_A];
i8 a1 = (i8) val;
i8 a2 = (i8) (1 - l6502_flag_get(e, L6502_FLAG_CARRY));
res = (u16) (u8) a0 - (u16) (u8) a1; res -= (u16) (u8) a2;
res &= 0xff;
if ((u8) a0 >= (u8) a1 + (u8) a2) res |= 1 << 8;
l6502_flag_set(e, L6502_FLAG_OVERFLOW, !((a0 >= 0) == (a1 >= 0)) && ((a0 >= 0) != ((i8) res >= 0)));
break;
}
break;
}
case 0b10: { /* group two */
switch ((ins >> 2) & 0b111) {
case G2_ADDRESSING_MODE_IMMEDIATE: addr = amode_immediate(e); break;
case G2_ADDRESSING_MODE_ZEROPAGE: addr = amode_zeropage(e); break;
case G2_ADDRESSING_MODE_ACCUMULATOR: addr = (u16) e->regs[L6502_REG_A]; set_reg = L6502_REG_A; break;
case G2_ADDRESSING_MODE_ABSOLUTE: addr = amode_absolute(e); break;
case G2_ADDRESSING_MODE_ZEROPAGE_X:
switch ((ins >> 5) & 0b111) {
case G2_OPCODE_LDX: case G2_OPCODE_STX: addr = amode_zeropage_y(e); break;
default: addr = amode_zeropage_x(e); break;
}
break;
case G2_ADDRESSING_MODE_ABSOLUTE_X:
switch ((ins >> 5) & 0b111) {
case G2_OPCODE_LDX: case G2_OPCODE_STX: addr = amode_absolute_y(e); break;
default: addr = amode_absolute_x(e); break;
}
break;
}
val = set_reg != L6502_REG__MARKER ? (u8) addr : l6502_read(e, addr);
switch ((ins >> 5) & 0b111) {
case G2_OPCODE_ASL:
nm = "asl";
set_carry = true;
res = ((u16) val) << 1;
break;
case G2_OPCODE_ROL:
nm = "rol";
set_carry = true;
res = ((u16) val) << 1 | (u16) l6502_flag_get(e, L6502_FLAG_CARRY);
break;
case G2_OPCODE_LSR:
nm = "lsr";
set_carry = true;
res = ((u16) val) >> 1
| (u16) (val & 0b1) << 8; /* place the previous low bit in carry */
break;
case G2_OPCODE_ROR:
nm = "ror";
set_carry = true;
res = ((u16) val) >> 1
| (u16) l6502_flag_get(e, L6502_FLAG_CARRY) << 7
| (u16) (val & 0b1) << 8; /* same as above */
break;
case G2_OPCODE_STX: nm = "stx"; l6502_write(e, addr, e->regs[L6502_REG_X]); set_flags = set_result = false; break;
case G2_OPCODE_LDX: nm = "ldx"; res = (u16) l6502_read(e, addr); set_reg = L6502_REG_X; break;
case G2_OPCODE_DEC: nm = "dec"; res = (u16) val - 1; break;
case G2_OPCODE_INC: nm = "inc"; res = (u16) val + 1; break;
}
break;
}
case 0b00: { /* group three */
switch ((ins >> 2) & 0b111) {
case G3_ADDRESSING_MODE_IMMEDIATE: addr = amode_immediate(e); break;
case G3_ADDRESSING_MODE_ZEROPAGE: addr = amode_zeropage(e); break;
case G3_ADDRESSING_MODE_ABSOLUTE: addr = amode_absolute(e); break;
case G3_ADDRESSING_MODE_ZEROPAGE_X: addr = amode_zeropage_x(e); break;
case G3_ADDRESSING_MODE_ABSOLUTE_X: addr = amode_absolute_x(e); break;
}
val = l6502_read(e, addr);
switch ((ins >> 5) & 0b111) {
case G3_OPCODE_BIT:
nm = "bit";
set_flags = set_result = false;
l6502_flag_set(e, L6502_FLAG_ZERO, (e->regs[L6502_REG_A] & val) == 0);
l6502_flag_set(e, L6502_FLAG_OVERFLOW, (val >> 6) & 0b1);
l6502_flag_set(e, L6502_FLAG_NEGATIVE, (val >> 7) & 0b1);
break;
case G3_OPCODE_STY: nm = "sty"; l6502_write(e, addr, e->regs[L6502_REG_Y]); set_flags = set_result = false; break;
case G3_OPCODE_LDY: nm = "ldy"; res = (u16) l6502_read(e, addr); set_reg = L6502_REG_Y; break;
case G3_OPCODE_CPY:
nm = "cpy";
set_result = false;
set_carry = true;
res = (u16) (u8) (e->regs[L6502_REG_Y] - val);
if (e->regs[L6502_REG_Y] >= val) res |= 1 << 8;
break;
case G3_OPCODE_CPX:
nm = "cpx";
set_result = false;
set_carry = true;
res = (u16) (u8) (e->regs[L6502_REG_X] - val);
if (e->regs[L6502_REG_X] >= val) res |= 1 << 8;
break;
}
}
}
if (set_flags) {
l6502_flag_set(e, L6502_FLAG_ZERO, (res & 0xff) == 0);
l6502_flag_set(e, L6502_FLAG_NEGATIVE, (res >> 7) & 0b1);
if (set_carry) l6502_flag_set(e, L6502_FLAG_CARRY, (res >> 8) != 0);
}
if (set_result) {
if (set_reg != L6502_REG__MARKER) e->regs[set_reg] = (u8) res;
else l6502_write(e, addr, (u8) res);
}
return nm;
}
|