Functioning vterm mode.
This creates a virtual terminal that the emulated program can get direct
character access to. It uses the same dimensions as the C64 terminal (40 rows,
25 cols). The screen of the virtual terminal is mapped to memory addresses
$FB00-$FEE7, and is in row-major format.
Will Haldean Brown
8 years ago
54 | 54 |
To disable these extensions, compile with
|
55 | 55 |
-DDISABLE_EXTENSIONS (right now, this can be done by
|
56 | 56 |
adding that flag to the Makefile).
|
|
57 |
|
|
58 |
This also implements a subset of the 65C02 and 65C816
|
|
59 |
instruction set, in particular the WAI (0xCB)
|
|
60 |
instruction. The WAI instruction pauses the emulator
|
|
61 |
until an I/O interrupt is thrown.
|
57 | 62 |
|
58 | 63 |
I/O memory map:
|
59 | 64 |
|
24 | 24 |
// branch_offset is an offset that will be added to the program counter
|
25 | 25 |
// after we move to the next instruction
|
26 | 26 |
int8_t branch_offset = 0;
|
|
27 |
|
|
28 |
// if set to true, the next instruction will not be executed until an
|
|
29 |
// interrupt is fired from the IO bus and handled.
|
|
30 |
uint8_t wait_for_interrupt = 0;
|
27 | 31 |
|
28 | 32 |
init_io();
|
29 | 33 |
|
|
55 | 59 |
#include "opcode_handlers/compare.h"
|
56 | 60 |
#include "opcode_handlers/flags.h"
|
57 | 61 |
#include "opcode_handlers/incdec.h"
|
|
62 |
#include "opcode_handlers/interrupts.h"
|
58 | 63 |
#include "opcode_handlers/jump.h"
|
59 | 64 |
#include "opcode_handlers/load.h"
|
60 | 65 |
#include "opcode_handlers/logical.h"
|
|
73 | 78 |
}
|
74 | 79 |
m->pc += branch_offset;
|
75 | 80 |
|
76 | |
handle_io(m);
|
|
81 |
do {
|
|
82 |
handle_io(m);
|
|
83 |
} while (wait_for_interrupt && !m->interrupt_waiting);
|
|
84 |
wait_for_interrupt = 0;
|
77 | 85 |
|
78 | 86 |
if (m->interrupt_waiting && !get_flag(m, FLAG_INTERRUPT)) {
|
79 | 87 |
STACK_PUSH(m) = (m->pc & 0xFF00) >> 8;
|
6 | 6 |
|
7 | 7 |
#include "functions.h"
|
8 | 8 |
|
9 | |
#define VTERM_ROWS 40
|
10 | |
#define VTERM_COLS 25
|
|
9 |
#define VTERM_ROWS 25
|
|
10 |
#define VTERM_COLS 40
|
11 | 11 |
|
12 | 12 |
uint8_t io_modeflags = 0x00;
|
|
13 |
|
|
14 |
WINDOW *window = NULL;
|
13 | 15 |
|
14 | 16 |
void init_vterm();
|
15 | 17 |
void update_vterm(cpu *, uint16_t);
|
|
32 | 34 |
initscr();
|
33 | 35 |
// reinit IO after initializing the ncurses window
|
34 | 36 |
init_io();
|
|
37 |
|
|
38 |
window = newwin(VTERM_ROWS + 2, VTERM_COLS + 2, 0, 0);
|
|
39 |
box(window, 0, 0);
|
35 | 40 |
}
|
36 | 41 |
|
37 | 42 |
void finish_vterm() {
|
38 | |
if (io_modeflags & IO_MODEFLAG_WAIT_TERMINATE) {
|
39 | |
// wait for user to hit enter to quit
|
40 | |
printw("\nterminated. hit any key to exit.");
|
41 | |
nodelay(stdscr, FALSE);
|
42 | |
getch();
|
43 | |
}
|
44 | 43 |
endwin();
|
45 | 44 |
}
|
46 | 45 |
|
|
62 | 61 |
// this is in the unused 24 bytes at the end of the page, ignore it
|
63 | 62 |
return;
|
64 | 63 |
}
|
65 | |
uint8_t r = offset / VTERM_COLS;
|
66 | |
uint8_t c = offset % VTERM_COLS;
|
67 | |
mvprintw(r, c, "%c", m->mem[dirty]);
|
|
64 |
// 1 offsets to avoid overwriting the border
|
|
65 |
uint8_t r = offset / VTERM_COLS + 1;
|
|
66 |
uint8_t c = offset % VTERM_COLS + 1;
|
|
67 |
mvwprintw(window, r, c, "%c", m->mem[dirty]);
|
|
68 |
wrefresh(window);
|
68 | 69 |
}
|
69 | 70 |
|
70 | 71 |
void handle_io(cpu *m) {
|
|
78 | 79 |
uint16_t addr = m->dirty_mem_addr;
|
79 | 80 |
if (addr == IO_PUTCHAR) {
|
80 | 81 |
if (io_modeflags & IO_MODEFLAG_VTERM) {
|
81 | |
addch(m->mem[addr]);
|
82 | |
refresh();
|
|
82 |
wprintw(window, "%c", m->mem[addr]);
|
|
83 |
wrefresh(window);
|
83 | 84 |
} else {
|
84 | 85 |
printf("%c", m->mem[addr]);
|
85 | 86 |
}
|
8 | 8 |
#define IO_VTERM_START 0xFB00
|
9 | 9 |
#define IO_VTERM_END 0xFF00
|
10 | 10 |
|
11 | |
#define IO_MODEFLAG_WAIT_TERMINATE 0x01
|
12 | |
#define IO_MODEFLAG_VTERM 0x02
|
|
11 |
#define IO_MODEFLAG_VTERM 0x01
|
13 | 12 |
|
14 | 13 |
void init_io();
|
15 | 14 |
void finish_io();
|
|
0 |
case BRK:
|
|
1 |
set_flag(m, FLAG_BREAK, 1);
|
|
2 |
m->interrupt_waiting = 1;
|
|
3 |
break;
|
|
4 |
|
|
5 |
case RTI:
|
|
6 |
m->sr = STACK_POP(m);
|
|
7 |
arg1 = STACK_POP(m);
|
|
8 |
m->pc = mem_abs(arg1, STACK_POP(m), 0);
|
|
9 |
break;
|
|
10 |
|
|
11 |
case WAI:
|
|
12 |
wait_for_interrupt = 1;
|
|
13 |
break;
|
23 | 23 |
arg1 = STACK_POP(m);
|
24 | 24 |
m->pc = mem_abs(arg1, STACK_POP(m), 0) + 1;
|
25 | 25 |
break;
|
26 | |
|
27 | |
case BRK:
|
28 | |
set_flag(m, FLAG_BREAK, 1);
|
29 | |
m->interrupt_waiting = 1;
|
30 | |
break;
|
31 | |
|
32 | |
case RTI:
|
33 | |
m->sr = STACK_POP(m);
|
34 | |
arg1 = STACK_POP(m);
|
35 | |
m->pc = mem_abs(arg1, STACK_POP(m), 0);
|
36 | |
break;
|
0 | 0 |
Test 6502 assembler programs. Generate binaries with:
|
1 | 1 |
|
2 | |
xa -bt0 -Istdlib/ sample_programs/test.s
|
|
2 |
xa -w -bt0 -Istdlib/ sample_programs/test.s
|
3 | 3 |
|
4 | 4 |
xa is a cross-assembler and utility suite for 65xx series processors. It's
|
5 | 5 |
included in many package repositories as `xa65', including Homebrew on OSX in
|
0 | 0 |
counter = $0100
|
|
1 |
supercounter = $0101
|
|
2 |
page = $FF
|
|
3 |
char = $FD
|
1 | 4 |
|
2 | |
lda #$03 ; set WAIT_TERMINATE flag
|
|
5 |
lda #$01 ; go into vterm mode
|
3 | 6 |
sta $FF02
|
4 | 7 |
|
5 | 8 |
lda #$FF
|
6 | 9 |
sta counter ; init counter
|
7 | 10 |
|
8 | |
ldx #$0
|
|
11 |
lda #$FB
|
|
12 |
sta page
|
|
13 |
lda #$00
|
|
14 |
sta page-1
|
9 | 15 |
|
10 | |
loop lda #$41 ; 41 == 'A'
|
11 | |
sta $FF00,X
|
12 | |
inx
|
13 | |
cpx counter
|
|
16 |
lda #$41 ; 41 == 'A'
|
|
17 |
sta char
|
|
18 |
|
|
19 |
ldy #$00
|
|
20 |
|
|
21 |
loop:
|
|
22 |
lda char
|
|
23 |
sta (page-1),Y
|
|
24 |
jsr incchar
|
|
25 |
|
|
26 |
iny
|
|
27 |
cpy #$00
|
14 | 28 |
bne loop
|
|
29 |
|
|
30 |
inc page
|
|
31 |
lda page
|
|
32 |
cmp #$FF
|
|
33 |
bne loop
|
|
34 |
|
|
35 |
wai
|
|
36 |
.byt $FF
|
|
37 |
|
|
38 |
incchar:
|
|
39 |
inc char
|
|
40 |
lda char
|
|
41 |
cmp #$5B
|
|
42 |
bne chardone
|
|
43 |
lda #$41
|
|
44 |
chardone:
|
|
45 |
sta char
|
|
46 |
rts
|