WIP 40x4 LCD: support in bios and emulator, minimal test rom, schematics
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
* PAT80 Emulator
|
||||
*
|
||||
* Emulates a PAT80.
|
||||
* Based on https://github.com/redcode/Z80
|
||||
*/
|
||||
|
||||
#include <Z/constants/pointer.h> /* Z_NULL */
|
||||
@ -29,7 +30,8 @@ typedef struct {
|
||||
Z80 cpu;
|
||||
WINDOW *terminal_win;
|
||||
WINDOW *status_win;
|
||||
WINDOW *lcd_win;
|
||||
WINDOW *lcd_top_win; // 40x4 LCD, top two lines
|
||||
WINDOW *lcd_bottom_win; // 40x4 LCD, bottom two lines
|
||||
} Machine;
|
||||
|
||||
|
||||
@ -50,8 +52,8 @@ static zuint8 machine_cpu_in(Machine *self, zuint16 port) {
|
||||
// so the 3 most significant IO addr bits in this case are A7, A6, A5. The bits
|
||||
// A4-A0 may be used by the single device, at its own discretion.
|
||||
zuint16 bitmask = 7; // 0000000000000111
|
||||
int decoded = port & bitmask;
|
||||
if (decoded <= 0x1F) {
|
||||
int ioDevice = port & bitmask;
|
||||
if (ioDevice <= 0x1F) {
|
||||
// Port 0 (0x00 to 0x1F): terminal
|
||||
// Read char from stin
|
||||
char c = getch();
|
||||
@ -66,37 +68,45 @@ static zuint8 machine_cpu_in(Machine *self, zuint16 port) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
if (decoded <= 0x3F) {
|
||||
if (ioDevice <= 0x3F) {
|
||||
// Port 1 (0x20 to 0x3F): sound card (sn76489)
|
||||
wprintw(self->status_win, "sound_cmd[IN]: Not supported!\n");
|
||||
return 0x00;
|
||||
}
|
||||
if (decoded <= 0x5F) {
|
||||
// Port 2 (0x40 to 0x5F)
|
||||
if (ioDevice <= 0x5F) {
|
||||
// Port 2 (0x40 to 0x5F): LCD top 2 lines
|
||||
if (port == 0x40 || port == 0x60) {
|
||||
// TODO: Simulate busy flag and cursor position
|
||||
return 0x00; // Busy flag clear, for now
|
||||
}
|
||||
wprintw(self->status_win, "IO_ERROR_IN: No device at port 2\n");
|
||||
return 0x00;
|
||||
}
|
||||
if (decoded <= 0x7F) {
|
||||
// Port 3 (0x60 to 0x7F)
|
||||
if (ioDevice <= 0x7F) {
|
||||
// Port 3 (0x60 to 0x7F): LCD bottom 2 lines
|
||||
if (port == 0x0) {
|
||||
// TODO: Simulate busy flag and cursor position
|
||||
return 0x00;
|
||||
}
|
||||
wprintw(self->status_win, "IO_ERROR_IN: No device at port 3\n");
|
||||
return 0x00;
|
||||
}
|
||||
if (decoded <= 0x9F) {
|
||||
if (ioDevice <= 0x9F) {
|
||||
// Port 4 (0x80 to 0x9F)
|
||||
wprintw(self->status_win, "IO_ERROR_IN: No device at port 4\n");
|
||||
return 0x00;
|
||||
}
|
||||
if (decoded <= 0x5F) {
|
||||
if (ioDevice <= 0x5F) {
|
||||
// Port 5 (0xA0 to 0xBF)
|
||||
wprintw(self->status_win, "IO_ERROR_IN: No device at port 5\n");
|
||||
return 0x00;
|
||||
}
|
||||
if (decoded <= 0x5F) {
|
||||
if (ioDevice <= 0x5F) {
|
||||
// Port 6 (0xC0 to 0xDF)
|
||||
wprintw(self->status_win, "IO_ERROR_IN: No device at port 6\n");
|
||||
return 0x00;
|
||||
}
|
||||
if (decoded <= 0x5F) {
|
||||
if (ioDevice <= 0x5F) {
|
||||
// Port 7 (0xE0 to 0xFF)
|
||||
wprintw(self->status_win, "IO_ERROR_IN: No device at port 7\n");
|
||||
} else {
|
||||
@ -107,7 +117,8 @@ static zuint8 machine_cpu_in(Machine *self, zuint16 port) {
|
||||
refresh();
|
||||
wrefresh(self->terminal_win);
|
||||
wrefresh(self->status_win);
|
||||
wrefresh(self->lcd_win);
|
||||
wrefresh(self->lcd_top_win);
|
||||
wrefresh(self->lcd_bottom_win);
|
||||
}
|
||||
|
||||
|
||||
@ -117,29 +128,56 @@ static void machine_cpu_out(Machine *self, zuint16 port, zuint8 value) {
|
||||
// so the 3 most significant IO addr bits in this case are A7, A6, A5. The bits
|
||||
// A4-A0 may be used by the single device, at its own discretion.
|
||||
zuint16 bitmask = 0xE0; // 0000000011100000
|
||||
int decoded = port & bitmask;
|
||||
if (decoded <= 0x1F) {
|
||||
int ioDevice = port & bitmask;
|
||||
bitmask = 0x1F; // 0000000000011111
|
||||
int ioAddrInsideDevice = port & bitmask;
|
||||
wprintw(self->status_win, "[%#06x]DECODED[%#04x] - ", port, ioDevice);
|
||||
if (ioDevice <= 0x1F) {
|
||||
// Port 0 (0x00 to 0x1F): terminal
|
||||
wprintw(self->terminal_win, "%c", value);
|
||||
} else if (decoded <= 0x3F) {
|
||||
} else if (ioDevice <= 0x3F) {
|
||||
// Port 1 (0x20 to 0x3F): sound card (sn76489)
|
||||
wprintw(self->status_win, "sound_cmd[%#04x]\n", value);
|
||||
} else if (decoded <= 0x5F) {
|
||||
// Port 2 (0x40 to 0x5F): lcd display 40x4 (mod. TM404A, based on 2 KS0066 chips, each one controlling 2 rows)
|
||||
wprintw(self->lcd_win, "%c", value);
|
||||
} else if (decoded <= 0x7F) {
|
||||
// Port 3 (0x60 to 0x7F)
|
||||
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 3\n");
|
||||
} else if (decoded <= 0x9F) {
|
||||
} else if (ioDevice <= 0x5F) {
|
||||
// Port 2 (0x40 to 0x5F) and Port 3 (0x60 to 0x7F):
|
||||
// lcd display 40x4 (mod. TM404A, based on 2 KS0066 chips, each one controlling 2 rows,
|
||||
// top controller at port 2 and bottom at port 3).
|
||||
// Instruction register at each port first address, data register at second address
|
||||
if (ioAddrInsideDevice == 0) {
|
||||
// Port 2, A4 LOW = talking to LCD top 2 lines instruction register
|
||||
wprintw(self->status_win, "lcd_top_cmd[%#04x]\n", value);
|
||||
} else if (ioAddrInsideDevice == 1) {
|
||||
// Port 2, A4 HIGH = talking to LCD top 2 lines data register (writing text to screen)
|
||||
wprintw(self->status_win, "lcd_top_data[%#04x](%c)\n", value, value);
|
||||
wprintw(self->lcd_top_win, "%c", value);
|
||||
} else {
|
||||
wprintw(self->status_win, "IO_ERROR_OUT: lcd (top controller) does not listen at addr %#04x\n", ioAddrInsideDevice);
|
||||
}
|
||||
} else if (ioDevice <= 0x7F) {
|
||||
// Port 2 (0x40 to 0x5F) and Port 3 (0x60 to 0x7F):
|
||||
// lcd display 40x4 (mod. TM404A, based on 2 KS0066 chips, each one controlling 2 rows,
|
||||
// top controller at port 2 and bottom at port 3).
|
||||
// Instruction register at each port first address, data register at second address
|
||||
if (ioAddrInsideDevice == 0) {
|
||||
// Port 3, A4 LOW = talking to LCD bottom 2 lines instruction register
|
||||
wprintw(self->status_win, "lcd_bottom_cmd[%#04x]\n", value);
|
||||
} else if (ioAddrInsideDevice == 1) {
|
||||
// Port 3, A4 HIGH = talking to LCD bottom 2 lines data register (writing text to screen)
|
||||
wprintw(self->status_win, "lcd_bottom_data[%#04x](%c)\n", value, value);
|
||||
wprintw(self->lcd_bottom_win, "%c", value);
|
||||
} else {
|
||||
wprintw(self->status_win, "IO_ERROR_OUT: lcd (bottom controller) does not listen at addr %#04x\n", ioAddrInsideDevice);
|
||||
}
|
||||
} else if (ioDevice <= 0x9F) {
|
||||
// Port 4 (0x80 to 0x9F)
|
||||
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 4\n");
|
||||
} else if (decoded <= 0x5F) {
|
||||
} else if (ioDevice <= 0xBF) {
|
||||
// Port 5 (0xA0 to 0xBF)
|
||||
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 5\n");
|
||||
} else if (decoded <= 0x5F) {
|
||||
} else if (ioDevice <= 0xDF) {
|
||||
// Port 6 (0xC0 to 0xDF)
|
||||
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 6\n");
|
||||
} else if (decoded <= 0x5F) {
|
||||
} else if (ioDevice <= 0xFF) {
|
||||
// Port 7 (0xE0 to 0xFF)
|
||||
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 7\n");
|
||||
} else {
|
||||
@ -150,7 +188,21 @@ static void machine_cpu_out(Machine *self, zuint16 port, zuint8 value) {
|
||||
refresh();
|
||||
wrefresh(self->terminal_win);
|
||||
wrefresh(self->status_win);
|
||||
wrefresh(self->lcd_win);
|
||||
wrefresh(self->lcd_top_win);
|
||||
wrefresh(self->lcd_bottom_win);
|
||||
}
|
||||
|
||||
static void machine_cpu_halt(Machine *self, unsigned char signal) {
|
||||
wprintw(self->status_win, "HALTED (%d)\n", signal);
|
||||
// Refresh all windows
|
||||
refresh();
|
||||
wrefresh(self->terminal_win);
|
||||
wrefresh(self->status_win);
|
||||
wrefresh(self->lcd_top_win);
|
||||
wrefresh(self->lcd_bottom_win);
|
||||
// Wait an ESC before exiting
|
||||
while (getch() != 27) {}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
@ -163,7 +215,7 @@ void machine_initialize(Machine *self) {
|
||||
self->cpu.write = (Z80Write)machine_cpu_write;
|
||||
self->cpu.in = (Z80Read )machine_cpu_in;
|
||||
self->cpu.out = (Z80Write)machine_cpu_out;
|
||||
self->cpu.halt = Z_NULL;
|
||||
self->cpu.halt = (Z80Halt)machine_cpu_halt;
|
||||
self->cpu.nmia = Z_NULL;
|
||||
self->cpu.inta = Z_NULL;
|
||||
self->cpu.int_fetch = Z_NULL;
|
||||
@ -224,15 +276,18 @@ int main(int argc, char *argv[]) {
|
||||
/*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), // To right of terminal window
|
||||
.lcd_win = newwin(4, 40, INSTRUCTION_WINDOW_HEIGHT + TERMINAL_HEIGHT + SPACING_BETWEEN_WINDOWS, 0) // Below terminal window
|
||||
.lcd_top_win = newwin(2, 40, INSTRUCTION_WINDOW_HEIGHT + TERMINAL_HEIGHT + SPACING_BETWEEN_WINDOWS, 0), // Below terminal window
|
||||
.lcd_bottom_win = newwin(2, 40, INSTRUCTION_WINDOW_HEIGHT + TERMINAL_HEIGHT + SPACING_BETWEEN_WINDOWS + 2, 0)
|
||||
};
|
||||
|
||||
wbkgd(pat80.terminal_win, COLOR_PAIR(1)); // Ncurses: set terminal window color
|
||||
wbkgd(pat80.status_win, COLOR_PAIR(2));
|
||||
wbkgd(pat80.lcd_win, COLOR_PAIR(3));
|
||||
wbkgd(pat80.lcd_top_win, COLOR_PAIR(3));
|
||||
wbkgd(pat80.lcd_bottom_win, COLOR_PAIR(3));
|
||||
scrollok(pat80.terminal_win, TRUE); // Ncurses: Allow scrolling when reached end of window
|
||||
scrollok(pat80.status_win, TRUE);
|
||||
scrollok(pat80.lcd_win, FALSE);
|
||||
scrollok(pat80.lcd_top_win, FALSE);
|
||||
scrollok(pat80.lcd_bottom_win, FALSE);
|
||||
attron(A_BOLD); // Print instructions
|
||||
printw("Emulator commands\n");
|
||||
attroff(A_BOLD);
|
||||
@ -258,7 +313,8 @@ int main(int argc, char *argv[]) {
|
||||
// Stop ncurses
|
||||
delwin(pat80.terminal_win);
|
||||
delwin(pat80.status_win);
|
||||
delwin(pat80.lcd_win);
|
||||
delwin(pat80.lcd_top_win);
|
||||
delwin(pat80.lcd_bottom_win);
|
||||
endwin();
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user