2025-02-09 09:14:02 +01:00
/**
* PAT80 Emulator
*
* Emulates a PAT80 .
*/
2025-01-29 07:12:11 +01:00
# include <Z/constants/pointer.h> /* Z_NULL */
# include <Z80.h>
# include <string.h>
2025-01-30 08:05:06 +01:00
# include <stdlib.h>
2025-02-09 09:52:37 +01:00
# include <ncurses.h>
2025-01-28 08:59:53 +01:00
2025-01-29 07:12:11 +01:00
# define ROM_SIZE 0x8000 /* 32 KiB */
2025-01-30 08:05:06 +01:00
# define MEMORY_SIZE 0xFFFF /* 64 KiB */
2025-02-11 08:42:08 +01:00
# define TERMINAL_WIDTH 60
# define TERMINAL_HEIGHT 25
# define SPACING_BETWEEN_WINDOWS 3
# define INSTRUCTION_WINDOW_HEIGHT 3
2025-01-29 07:12:11 +01:00
typedef struct {
void * context ;
zuint8 ( * read ) ( void * context ) ;
void ( * write ) ( void * context , zuint8 value ) ;
} Device ;
typedef struct {
zusize cycles ;
zuint8 memory [ 65536 ] ;
Z80 cpu ;
2025-02-11 08:42:08 +01:00
WINDOW * terminal_win ;
WINDOW * status_win ;
2025-02-18 08:48:25 +01:00
WINDOW * lcd_win ;
2025-01-29 07:12:11 +01:00
} Machine ;
2025-01-30 08:11:49 +01:00
static zuint8 machine_cpu_read ( Machine * self , zuint16 address ) {
2025-01-29 07:12:11 +01:00
return address < MEMORY_SIZE ? self - > memory [ address ] : 0xFF ;
2025-01-30 08:11:49 +01:00
}
2025-01-29 07:12:11 +01:00
2025-01-30 08:11:49 +01:00
static void machine_cpu_write ( Machine * self , zuint16 address , zuint8 value ) {
2025-01-29 07:12:11 +01:00
if ( address > = ROM_SIZE & & address < MEMORY_SIZE )
self - > memory [ address ] = value ;
2025-01-30 08:11:49 +01:00
}
2025-01-29 07:12:11 +01:00
2025-01-30 08:11:49 +01:00
static zuint8 machine_cpu_in ( Machine * self , zuint16 port ) {
2025-02-07 08:18:14 +01:00
// Pat80 has 8 devices, decoded based on the 3 most significant IO addr bits.
// Note the Z80 has 16 bit address bus, but only the first 8 are used as IO addr,
// 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 ) {
// Port 0 (0x00 to 0x1F): terminal
2025-02-09 09:52:37 +01:00
// Read char from stin
2025-02-11 08:42:08 +01:00
char c = getch ( ) ;
// 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 ;
}
2025-02-07 08:18:14 +01:00
}
if ( decoded < = 0x3F ) {
// Port 1 (0x20 to 0x3F): sound card (sn76489)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " sound_cmd[IN]: Not supported! \n " ) ;
2025-02-07 08:18:14 +01:00
return 0x00 ;
}
if ( decoded < = 0x5F ) {
// Port 2 (0x40 to 0x5F)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_IN: No device at port 2 \n " ) ;
2025-02-07 08:18:14 +01:00
return 0x00 ;
}
if ( decoded < = 0x7F ) {
// Port 3 (0x60 to 0x7F)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_IN: No device at port 3 \n " ) ;
2025-02-07 08:18:14 +01:00
return 0x00 ;
}
if ( decoded < = 0x9F ) {
// Port 4 (0x80 to 0x9F)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_IN: No device at port 4 \n " ) ;
2025-02-07 08:18:14 +01:00
return 0x00 ;
}
if ( decoded < = 0x5F ) {
// Port 5 (0xA0 to 0xBF)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_IN: No device at port 5 \n " ) ;
2025-02-07 08:18:14 +01:00
return 0x00 ;
}
if ( decoded < = 0x5F ) {
// Port 6 (0xC0 to 0xDF)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_IN: No device at port 6 \n " ) ;
2025-02-07 08:18:14 +01:00
return 0x00 ;
}
if ( decoded < = 0x5F ) {
// Port 7 (0xE0 to 0xFF)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_IN: No device at port 7 \n " ) ;
2025-02-07 08:18:14 +01:00
} else {
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_IN: Invalid port address: %#04x \n " , port ) ;
2025-02-07 08:18:14 +01:00
}
2025-02-11 08:42:08 +01:00
// TODO: place this in a refresh cycle between CPU instructions
refresh ( ) ;
wrefresh ( self - > terminal_win ) ;
wrefresh ( self - > status_win ) ;
2025-02-18 08:48:25 +01:00
wrefresh ( self - > lcd_win ) ;
2025-01-30 08:11:49 +01:00
}
2025-01-29 07:12:11 +01:00
2025-01-30 08:11:49 +01:00
static void machine_cpu_out ( Machine * self , zuint16 port , zuint8 value ) {
2025-02-07 08:18:14 +01:00
// Pat80 has 8 devices, decoded based on the 3 most significant IO addr bits.
// Note the Z80 has 16 bit address bus, but only the first 8 are used as IO addr,
// 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 ) {
// Port 0 (0x00 to 0x1F): terminal
2025-02-11 08:42:08 +01:00
wprintw ( self - > terminal_win , " %c " , value ) ;
2025-02-07 08:18:14 +01:00
} else if ( decoded < = 0x3F ) {
// Port 1 (0x20 to 0x3F): sound card (sn76489)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " sound_cmd[%#04x] \n " , value ) ;
2025-02-07 08:18:14 +01:00
} else if ( decoded < = 0x5F ) {
2025-02-18 08:48:25 +01:00
// 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 ) ;
2025-02-07 08:18:14 +01:00
} else if ( decoded < = 0x7F ) {
// Port 3 (0x60 to 0x7F)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_OUT: No device at port 3 \n " ) ;
2025-02-07 08:18:14 +01:00
} else if ( decoded < = 0x9F ) {
// Port 4 (0x80 to 0x9F)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_OUT: No device at port 4 \n " ) ;
2025-02-07 08:18:14 +01:00
} else if ( decoded < = 0x5F ) {
// Port 5 (0xA0 to 0xBF)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_OUT: No device at port 5 \n " ) ;
2025-02-07 08:18:14 +01:00
} else if ( decoded < = 0x5F ) {
// Port 6 (0xC0 to 0xDF)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_OUT: No device at port 6 \n " ) ;
2025-02-07 08:18:14 +01:00
} else if ( decoded < = 0x5F ) {
// Port 7 (0xE0 to 0xFF)
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_OUT: No device at port 7 \n " ) ;
2025-02-07 08:18:14 +01:00
} else {
2025-02-11 08:42:08 +01:00
wprintw ( self - > status_win , " IO_ERROR_OUT: Invalid port address: %#04x \n " , port ) ;
2025-02-07 08:18:14 +01:00
}
2025-02-11 08:42:08 +01:00
// TODO: place this in a refresh cycle between CPU instructions
refresh ( ) ;
wrefresh ( self - > terminal_win ) ;
wrefresh ( self - > status_win ) ;
2025-02-18 08:48:25 +01:00
wrefresh ( self - > lcd_win ) ;
2025-01-30 08:11:49 +01:00
}
2025-01-29 07:12:11 +01:00
2025-01-30 08:11:49 +01:00
void machine_initialize ( Machine * self ) {
2025-01-29 07:12:11 +01:00
self - > cpu . context = self ;
self - > cpu . fetch_opcode =
self - > cpu . fetch =
self - > cpu . nop =
self - > cpu . read = ( Z80Read ) machine_cpu_read ;
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 . nmia = Z_NULL ;
self - > cpu . inta = Z_NULL ;
self - > cpu . int_fetch = Z_NULL ;
self - > cpu . ld_i_a = Z_NULL ;
self - > cpu . ld_r_a = Z_NULL ;
self - > cpu . reti = Z_NULL ;
self - > cpu . retn = Z_NULL ;
self - > cpu . hook = Z_NULL ;
self - > cpu . illegal = Z_NULL ;
self - > cpu . options = Z80_MODEL_ZILOG_NMOS ;
2025-01-30 08:11:49 +01:00
}
2025-01-29 07:12:11 +01:00
2025-01-30 08:11:49 +01:00
void machine_power ( Machine * self , zbool state ) {
2025-01-29 07:12:11 +01:00
if ( state )
{
self - > cycles = 0 ;
2025-02-07 08:18:14 +01:00
memset ( self - > memory , 0 , MEMORY_SIZE ) ;
2025-01-29 07:12:11 +01:00
}
z80_power ( & self - > cpu , state ) ;
2025-01-30 08:11:49 +01:00
}
2025-01-29 07:12:11 +01:00
2025-01-30 08:11:49 +01:00
void machine_reset ( Machine * self ) {
2025-01-29 07:12:11 +01:00
z80_instant_reset ( & self - > cpu ) ;
2025-01-30 08:11:49 +01:00
}
2025-01-29 07:12:11 +01:00
2025-01-30 08:05:06 +01:00
void machine_run ( Machine * self ) {
z80_run ( & self - > cpu , Z80_MAXIMUM_CYCLES ) ;
}
2025-01-29 07:12:11 +01:00
2025-01-30 08:05:06 +01:00
int main ( int argc , char * argv [ ] ) {
// Parse arguments
if ( argc < 2 ) {
printf ( " Usage: %s [romFile] \n " , argv [ 0 ] ) ;
exit ( 0 ) ;
2025-01-29 07:12:11 +01:00
}
2025-01-30 08:05:06 +01:00
char * romFilePath = argv [ 1 ] ;
2025-01-29 07:12:11 +01:00
2025-02-11 08:42:08 +01:00
// 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
2025-02-18 08:48:25 +01:00
init_pair ( 3 , COLOR_BLACK , COLOR_GREEN ) ; // LCD window color
2025-02-11 08:42:08 +01:00
int x , y ;
getmaxyx ( stdscr , y , x ) ;
2025-01-29 07:12:11 +01:00
// Setup virtual Pat80 computer
Z80 pat80Cpu = { } ;
2025-02-07 08:18:14 +01:00
Machine pat80 = {
2025-02-09 09:14:02 +01:00
/*zusize*/ . cycles = 0 ,
2025-02-11 08:42:08 +01:00
/*Z80*/ . cpu = pat80Cpu ,
. terminal_win = newwin ( TERMINAL_HEIGHT , TERMINAL_WIDTH , INSTRUCTION_WINDOW_HEIGHT , 0 ) ,
2025-02-18 08:48:25 +01:00
. 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
2025-02-07 08:18:14 +01:00
} ;
2025-02-11 08:42:08 +01:00
wbkgd ( pat80 . terminal_win , COLOR_PAIR ( 1 ) ) ; // Ncurses: set terminal window color
wbkgd ( pat80 . status_win , COLOR_PAIR ( 2 ) ) ;
2025-02-18 08:48:25 +01:00
wbkgd ( pat80 . lcd_win , COLOR_PAIR ( 3 ) ) ;
2025-02-11 08:42:08 +01:00
scrollok ( pat80 . terminal_win , TRUE ) ; // Ncurses: Allow scrolling when reached end of window
scrollok ( pat80 . status_win , TRUE ) ;
2025-02-18 08:48:25 +01:00
scrollok ( pat80 . lcd_win , FALSE ) ;
2025-02-11 08:42:08 +01:00
attron ( A_BOLD ) ; // Print instructions
printw ( " Emulator commands \n " ) ;
attroff ( A_BOLD ) ;
printw ( " ESC: Exit " ) ;
2025-02-07 08:18:14 +01:00
machine_initialize ( & pat80 ) ;
machine_power ( & pat80 , Z_TRUE ) ;
2025-01-29 07:12:11 +01:00
2025-01-30 08:05:06 +01:00
// Load ROM into memory
FILE * romFile ;
romFile = fopen ( romFilePath , " rb " ) ;
if ( romFile = = NULL ) {
printf ( " Unable to open rom file at %s " , romFilePath ) ;
exit ( 1 ) ;
}
2025-02-07 08:18:14 +01:00
fread ( & pat80 . memory , ROM_SIZE , 1 , romFile ) ; // load rom from file into memory, up >
2025-01-30 08:05:06 +01:00
fclose ( romFile ) ;
2025-02-09 09:52:37 +01:00
// Start emulated computer
2025-01-29 07:12:11 +01:00
machine_reset ( & pat80 ) ;
2025-01-30 08:05:06 +01:00
machine_run ( & pat80 ) ;
2025-02-09 09:52:37 +01:00
// Stop ncurses
2025-02-11 08:42:08 +01:00
delwin ( pat80 . terminal_win ) ;
delwin ( pat80 . status_win ) ;
2025-02-18 08:48:25 +01:00
delwin ( pat80 . lcd_win ) ;
2025-02-11 08:42:08 +01:00
endwin ( ) ;
2025-02-09 09:52:37 +01:00
return 0 ;
2025-01-30 08:11:49 +01:00
}