git.haldean.org x6502 / a7052d4
Fix branch behavior, add debug instruction. Turns out you're supposed to branch relative to the start of the /next/ instruction, not the current one. Whoops. Will Haldean Brown 8 years ago
8 changed file(s) with 70 addition(s) and 37 deletion(s). Raw diff Collapse all Expand all
00 case BCC_REL:
11 s1 = NEXT_BYTE(m);
22 if (!get_flag(m, FLAG_CARRY)) {
3 m->pc += s1;
3 branch_offset = s1;
44 }
55 break;
66
77 case BCS_REL:
88 s1 = NEXT_BYTE(m);
99 if (get_flag(m, FLAG_CARRY)) {
10 m->pc += s1;
10 branch_offset = s1;
1111 }
1212 break;
1313
1414 case BEQ_REL:
1515 s1 = NEXT_BYTE(m);
1616 if (get_flag(m, FLAG_ZERO)) {
17 m->pc += s1;
17 branch_offset = s1;
1818 }
1919 break;
2020
2121 case BMI_REL:
2222 s1 = NEXT_BYTE(m);
2323 if (get_flag(m, FLAG_NEGATIVE)) {
24 m->pc += s1;
24 branch_offset = s1;
2525 }
2626 break;
2727
2828 case BNE_REL:
2929 s1 = NEXT_BYTE(m);
3030 if (!get_flag(m, FLAG_ZERO)) {
31 m->pc += s1;
31 branch_offset = s1;
3232 }
3333 break;
3434
3535 case BPL_REL:
3636 s1 = NEXT_BYTE(m);
3737 if (!get_flag(m, FLAG_NEGATIVE)) {
38 m->pc += s1;
38 branch_offset = s1;
3939 }
4040 break;
4141
4242 case BVC_REL:
4343 s1 = NEXT_BYTE(m);
4444 if (!get_flag(m, FLAG_OVERFLOW)) {
45 m->pc += s1;
45 branch_offset = s1;
4646 }
4747 break;
4848
4949 case BVS_REL:
5050 s1 = NEXT_BYTE(m);
5151 if (get_flag(m, FLAG_OVERFLOW)) {
52 m->pc += s1;
52 branch_offset = s1;
5353 }
5454 break;
2929 uint8_t sr;
3030 // emulator flag register (not in 6502 spec, not accessible from assembler)
3131 uint8_t emu_flags;
32
32 // set to nonzero if there is an outstanding interrupt
3333 uint8_t interrupt_waiting;
34
35 #ifdef DEBUG
36 uint8_t last_opcode;
37 #endif
38
3934 // RAM
4035 uint8_t mem[MEMORY_SIZE];
41
4236 // stores the address of memory modified by the last instruction
4337 uint16_t dirty_mem_addr;
38 // the opcode of the last instruction run. for debugging only.
39 uint8_t last_opcode;
4440 } cpu;
4541
4642 cpu * new_cpu();
22 #include <stdio.h>
33
44 #define MEM_PRINT_BYTES 16
5 #define MAX_MEM_OFFSET (MEMORY_SIZE - MEM_PRINT_BYTES)
56
67 void dump_cpu(cpu *m) {
7 /* even though this method is never called if the debug switch is off,
8 * we want to be able to access members in here that don't exist when DEBUG
9 * is off, so we have to no-op the method in the debug-undefined case. */
10 #ifdef DEBUG
118 init_names();
129
1310 int i;
2926 (m->sr & FLAG_CARRY) > 0);
3027
3128 printf("\nmem ");
29
30 int mem_offset = m->pc - MEM_PRINT_BYTES / 2;
31 // clamp to [0, MAX_MEM_OFFSET]
32 mem_offset = mem_offset < 0 ? 0 : (
33 mem_offset > MAX_MEM_OFFSET ? MAX_MEM_OFFSET : mem_offset);
34
3235 for (i = 0; i < MEM_PRINT_BYTES; i++) {
33 printf("%02X ", m->mem[i]);
36 printf("%02X ", m->mem[i + mem_offset]);
3437 }
35 if (m->pc < MEM_PRINT_BYTES) {
36 printf("\n ");
37 for (i = 0; i < m->pc; i++) {
38 printf(" ");
39 }
40 printf("^^");
38
39 printf("\n ");
40 for (i = 0; i < m->pc - mem_offset; i++) {
41 printf(" ");
4142 }
43 printf("^^ (%04x)", m->pc);
4244
4345 printf("\nstack ");
4446 for (i = 0; i < MEM_PRINT_BYTES; i++) {
5456 }
5557
5658 printf("\n\n");
57 #endif
5859 }
33 #include "cpu.h"
44
55 #ifdef DEBUG
6 #define DUMP(cpu) (dump_cpu(cpu))
6 #define DUMP_DEBUG(cpu) (dump_cpu(cpu))
77 #else
8 #define DUMP(cpu)
8 #define DUMP_DEBUG(cpu)
99 #endif
1010
1111 void dump_cpu(cpu *m);
2121 uint8_t pc_offset = 0;
2222 uint16_t pc_start;
2323
24 // branch_offset is an offset that will be added to the program counter
25 // after we move to the next instruction
26 int8_t branch_offset = 0;
27
2428 init_io();
2529
2630 for (;;) {
27 DUMP(m);
31 DUMP_DEBUG(m);
2832
2933 reset_emu_flags(m);
3034
3135 pc_offset = 0;
36 branch_offset = 0;
3237 pc_start = m->pc;
3338 opcode = NEXT_BYTE(m);
3439
3641 case NOP:
3742 break;
3843
44 #ifndef DISABLE_EXTENSIONS
3945 case EXT:
4046 goto end;
47
48 case DUMP:
49 dump_cpu(m);
50 break;
51 #endif
4152
4253 #include "arithmetic.h"
4354 #include "branch.h"
6071 if (m->pc == pc_start) {
6172 m->pc += pc_offset;
6273 }
74 m->pc += branch_offset;
6375
6476 handle_io(m);
6577
66 if (m->interrupt_waiting && get_flag(m, FLAG_INTERRUPT)) {
78 if (m->interrupt_waiting && !get_flag(m, FLAG_INTERRUPT)) {
6779 STACK_PUSH(m) = (m->pc & 0xFF00) >> 8;
6880 STACK_PUSH(m) = m->pc & 0xFF;
6981 STACK_PUSH(m) = m->sr;
7385 m->sr |= FLAG_INTERRUPT;
7486 }
7587
76 #ifdef DEBUG
7788 m->last_opcode = opcode;
78 #endif
7989 }
8090 end:
8191 finish_io();
5454
5555 void handle_io(cpu *m) {
5656 if (next_read != next_write) {
57 printf("\nsending interrupt for char %c\n", input_buffer[next_read]);
5857 m->interrupt_waiting = 0x01;
5958 m->mem[IO_GETCHAR] = input_buffer[next_read++];
6059 if (next_read >= INPUT_BUFFER_SIZE) {
6665 if (m->dirty_mem_addr == IO_PUTCHAR) {
6766 uint8_t val = m->mem[m->dirty_mem_addr];
6867 putchar(val);
68 fflush(stdout);
6969 }
7070 }
7171 }
2727 // ZPX: zero-page,X, add next byte to X modulo 0xFF and use that as a
2828 // memory address
2929
30 // custom instruction set extensions
31 #define DUMP 0xFC
3032 #define EXT 0xFF
3133
3234 #define ADC_AB 0x6D
0 ; load location of ISR and enable interrupts
01 lda #<isr
12 sta $FFFE
23 lda #>isr
3 sta $FFFF ; set the location of the isr
4 sta $FFFF
5 cli
6
7 ; wait for input
48 loop nop
59 jmp loop
10
11 ; start ISR
612 isr pha
7 lda $FF01
8 sta $FF00
13
14 ; check if char is less than 'a'
15 cheka ldx #$60 ; == 'a' - 1
16 cpx $FF01
17 .byt $FC ; dump command
18 bcs chekz ; if val is >= a, all good so far, skip to z check
19 jmp noup ; if val is < a, don't transform it
20
21 ; check if char is greater than 'z'
22 chekz ldx #$7B ; == 'z' + 1
23 cpx $FF01
24 .byt $FC ; dump command
25 bcs noup ; if val is >= z + 1, don't transform it
26
27 lda #$E0 ; 'A' - 'a'
28 adc $FF01
29 jmp done
30
31 noup lda $FF01
32 done sta $FF00
933 pla
1034 rti
1135 .word $0000, loop