3 Commits

Author SHA1 Message Date
24158df261 Emulator: separated terminal and status windows 2025-02-11 08:42:08 +01:00
ed3ed7bd03 Emulator: ncurses 2025-02-09 09:52:37 +01:00
ec678cb3d0 Cleanup 2025-02-09 09:14:02 +01:00
2 changed files with 88 additions and 88 deletions

View File

@ -5,3 +5,7 @@ add_executable(pat80-emulator main.c)
find_package(Z80 REQUIRED) find_package(Z80 REQUIRED)
target_link_libraries(pat80-emulator Z80) target_link_libraries(pat80-emulator Z80)
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR})
target_link_libraries(pat80-emulator ${CURSES_LIBRARIES})

View File

@ -1,14 +1,21 @@
/**
* PAT80 Emulator
*
* Emulates a PAT80.
*/
#include <Z/constants/pointer.h> /* Z_NULL */ #include <Z/constants/pointer.h> /* Z_NULL */
#include <Z80.h> #include <Z80.h>
#include <string.h> #include <string.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <ncurses.h>
#define CYCLES_PER_FRAME 69888
#define CYCLES_AT_INT 24
#define CYCLES_PER_INT 32
#define ROM_SIZE 0x8000 /* 32 KiB */ #define ROM_SIZE 0x8000 /* 32 KiB */
#define MEMORY_SIZE 0xFFFF /* 64 KiB */ #define MEMORY_SIZE 0xFFFF /* 64 KiB */
#define TERMINAL_WIDTH 60
#define TERMINAL_HEIGHT 25
#define SPACING_BETWEEN_WINDOWS 3
#define INSTRUCTION_WINDOW_HEIGHT 3
typedef struct { typedef struct {
void* context; void* context;
@ -20,8 +27,8 @@ typedef struct {
zusize cycles; zusize cycles;
zuint8 memory[65536]; zuint8 memory[65536];
Z80 cpu; Z80 cpu;
Device* devices; WINDOW *terminal_win;
zusize device_count; WINDOW *status_win;
} Machine; } Machine;
@ -45,46 +52,60 @@ static zuint8 machine_cpu_in(Machine *self, zuint16 port) {
int decoded = port & bitmask; int decoded = port & bitmask;
if (decoded <= 0x1F) { if (decoded <= 0x1F) {
// Port 0 (0x00 to 0x1F): terminal // Port 0 (0x00 to 0x1F): terminal
//printf("TERMINAL_READ"); // Read char from stin
//return 'H'; char c = getch();
return 0x00; // Intercept emulator commands
switch (c) {
case 27:
// ESC: shutdown emulator
exit(0); // TODO: Shutdown ncurses and emulator cleanly
return 0;
default:
// Deliver keypress to pat80
return c;
}
} }
if (decoded <= 0x3F) { if (decoded <= 0x3F) {
// Port 1 (0x20 to 0x3F): sound card (sn76489) // Port 1 (0x20 to 0x3F): sound card (sn76489)
printf("\nsound_cmd[IN]: Not supported!\n"); wprintw(self->status_win, "sound_cmd[IN]: Not supported!\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x5F) { if (decoded <= 0x5F) {
// Port 2 (0x40 to 0x5F) // Port 2 (0x40 to 0x5F)
printf("IO_ERROR_IN: No device at port 2\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 2\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x7F) { if (decoded <= 0x7F) {
// Port 3 (0x60 to 0x7F) // Port 3 (0x60 to 0x7F)
printf("IO_ERROR_IN: No device at port 3\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 3\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x9F) { if (decoded <= 0x9F) {
// Port 4 (0x80 to 0x9F) // Port 4 (0x80 to 0x9F)
printf("IO_ERROR_IN: No device at port 4\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 4\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x5F) { if (decoded <= 0x5F) {
// Port 5 (0xA0 to 0xBF) // Port 5 (0xA0 to 0xBF)
printf("IO_ERROR_IN: No device at port 5\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 5\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x5F) { if (decoded <= 0x5F) {
// Port 6 (0xC0 to 0xDF) // Port 6 (0xC0 to 0xDF)
printf("IO_ERROR_IN: No device at port 6\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 6\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x5F) { if (decoded <= 0x5F) {
// Port 7 (0xE0 to 0xFF) // Port 7 (0xE0 to 0xFF)
printf("IO_ERROR_IN: No device at port 7\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 7\n");
} else { } else {
printf("IO_ERROR_IN: Invalid port address: %#04x\n", port); wprintw(self->status_win, "IO_ERROR_IN: Invalid port address: %#04x\n", port);
} }
// TODO: place this in a refresh cycle between CPU instructions
refresh();
wrefresh(self->terminal_win);
wrefresh(self->status_win);
} }
@ -97,31 +118,36 @@ static void machine_cpu_out(Machine *self, zuint16 port, zuint8 value) {
int decoded = port & bitmask; int decoded = port & bitmask;
if (decoded <= 0x1F) { if (decoded <= 0x1F) {
// Port 0 (0x00 to 0x1F): terminal // Port 0 (0x00 to 0x1F): terminal
printf("%c", value); wprintw(self->terminal_win, "%c", value);
} else if (decoded <= 0x3F) { } else if (decoded <= 0x3F) {
// Port 1 (0x20 to 0x3F): sound card (sn76489) // Port 1 (0x20 to 0x3F): sound card (sn76489)
printf("\nsound_cmd[%#04x]\n", value); wprintw(self->status_win, "sound_cmd[%#04x]\n", value);
} else if (decoded <= 0x5F) { } else if (decoded <= 0x5F) {
// Port 2 (0x40 to 0x5F) // Port 2 (0x40 to 0x5F)
printf("IO_ERROR_OUT: No device at port 2\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 2\n");
} else if (decoded <= 0x7F) { } else if (decoded <= 0x7F) {
// Port 3 (0x60 to 0x7F) // Port 3 (0x60 to 0x7F)
printf("IO_ERROR_OUT: No device at port 3\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 3\n");
} else if (decoded <= 0x9F) { } else if (decoded <= 0x9F) {
// Port 4 (0x80 to 0x9F) // Port 4 (0x80 to 0x9F)
printf("IO_ERROR_OUT: No device at port 4\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 4\n");
} else if (decoded <= 0x5F) { } else if (decoded <= 0x5F) {
// Port 5 (0xA0 to 0xBF) // Port 5 (0xA0 to 0xBF)
printf("IO_ERROR_OUT: No device at port 5\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 5\n");
} else if (decoded <= 0x5F) { } else if (decoded <= 0x5F) {
// Port 6 (0xC0 to 0xDF) // Port 6 (0xC0 to 0xDF)
printf("IO_ERROR_OUT: No device at port 6\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 6\n");
} else if (decoded <= 0x5F) { } else if (decoded <= 0x5F) {
// Port 7 (0xE0 to 0xFF) // Port 7 (0xE0 to 0xFF)
printf("IO_ERROR_OUT: No device at port 7\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 7\n");
} else { } else {
printf("IO_ERROR_OUT: Invalid port address: %#04x\n", port); wprintw(self->status_win, "IO_ERROR_OUT: Invalid port address: %#04x\n", port);
} }
// TODO: place this in a refresh cycle between CPU instructions
refresh();
wrefresh(self->terminal_win);
wrefresh(self->status_win);
} }
@ -145,8 +171,6 @@ void machine_initialize(Machine *self) {
self->cpu.hook = Z_NULL; self->cpu.hook = Z_NULL;
self->cpu.illegal = Z_NULL; self->cpu.illegal = Z_NULL;
self->cpu.options = Z80_MODEL_ZILOG_NMOS; self->cpu.options = Z80_MODEL_ZILOG_NMOS;
/* Create and initialize devices... */
} }
@ -169,47 +193,6 @@ void machine_run(Machine *self) {
z80_run(&self->cpu, Z80_MAXIMUM_CYCLES); z80_run(&self->cpu, Z80_MAXIMUM_CYCLES);
} }
// void machine_run_frame(Machine *self)
// {
// /* CPU cycles before the INT signal */
// self->cycles += z80_execute(&self->cpu, CYCLES_AT_INT - self->cycles);
// /* CPU cycles during the INT signal */
// z80_int(&self->cpu, Z_TRUE);
// self->cycles += z80_run(&self->cpu, (CYCLES_AT_INT + CYCLES_PER_INT) - self->cycles);
// z80_int(&self->cpu, Z_FALSE);
// /* CPU cycles after the INT signal */
// self->cycles += z80_execute(&self->cpu, CYCLES_PER_FRAME - self->cycles);
// self->cycles -= CYCLES_PER_FRAME;
// }
// TEST IMPLEM DEVICE
zuint8 device_terminal_read(void *context) {
return 'H';
//return 0;
}
void device_terminal_write(void *context, zuint8 value) {
printf("%c", value);
}
zuint8 device_sound_read(void *context) {
return 0;
}
void device_sound_write(void *context, zuint8 value) {
printf("sound[%c]\n", value);
}
// TEST IMPLEM DEVICE
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Parse arguments // Parse arguments
if (argc < 2) { if (argc < 2) {
@ -218,13 +201,36 @@ int main(int argc, char *argv[]) {
} }
char* romFilePath = argv[1]; char* romFilePath = argv[1];
// Init ncurses
initscr(); // Start curses mode
raw(); // Line buffering disabled (get character without waiting for ENTER key)
keypad(stdscr, TRUE); // We get F1, F2 etc..
noecho(); // Don't echo() while we do getch
start_color(); // Use colors
init_pair(1, COLOR_WHITE, COLOR_BLUE); // Terminal window color
init_pair(2, COLOR_YELLOW, COLOR_BLACK); // Status window color
int x,y;
getmaxyx(stdscr, y,x);
// Setup virtual Pat80 computer // Setup virtual Pat80 computer
Z80 pat80Cpu = {}; Z80 pat80Cpu = {};
Machine pat80 = { Machine pat80 = {
/*zusize*/ .cycles = 100000, /*zusize*/ .cycles = 0,
/*Z80*/ .cpu = pat80Cpu /*Z80*/ .cpu = pat80Cpu,
.terminal_win = newwin(TERMINAL_HEIGHT, TERMINAL_WIDTH, INSTRUCTION_WINDOW_HEIGHT, 0),
.status_win = newwin(y, x - TERMINAL_WIDTH - SPACING_BETWEEN_WINDOWS, INSTRUCTION_WINDOW_HEIGHT, TERMINAL_WIDTH + SPACING_BETWEEN_WINDOWS)
}; };
wbkgd(pat80.terminal_win, COLOR_PAIR(1)); // Ncurses: set terminal window color
wbkgd(pat80.status_win, COLOR_PAIR(2));
scrollok(pat80.terminal_win, TRUE); // Ncurses: Allow scrolling when reached end of window
scrollok(pat80.status_win, TRUE);
attron(A_BOLD); // Print instructions
printw("Emulator commands\n");
attroff(A_BOLD);
printw("ESC: Exit");
machine_initialize(&pat80); machine_initialize(&pat80);
machine_power(&pat80, Z_TRUE); machine_power(&pat80, Z_TRUE);
@ -238,23 +244,13 @@ int main(int argc, char *argv[]) {
fread(&pat80.memory,ROM_SIZE,1,romFile); // load rom from file into memory, up > fread(&pat80.memory,ROM_SIZE,1,romFile); // load rom from file into memory, up >
fclose(romFile); fclose(romFile);
// Declare system devices // Start emulated computer
Device patoTerminal = {
.read = *device_terminal_read,
.write = *device_terminal_write,
};
Device patoSound = {
.read = *device_sound_read,
.write = *device_sound_write,
};
Device patoDevices[2] = {
patoTerminal, // Port 0 (0x00 to 0x1F)
patoSound // Port 1 (0x20 to 0x3F)
};
pat80.devices = patoDevices;
pat80.device_count = 2;
machine_reset(&pat80); machine_reset(&pat80);
machine_run(&pat80); machine_run(&pat80);
// Stop ncurses
delwin(pat80.terminal_win);
delwin(pat80.status_win);
endwin();
return 0;
} }