diff options
-rw-r--r-- | src/czx81.c | 419 |
1 files changed, 418 insertions, 1 deletions
diff --git a/src/czx81.c b/src/czx81.c index 58d3bcb..6ba48a8 100644 --- a/src/czx81.c +++ b/src/czx81.c @@ -18,12 +18,410 @@ #include "wide_curses.h" #include <stdlib.h> +#include <stdio.h> #include <unistd.h> #include <time.h> #include <locale.h> +#include <string.h> #include "z80.h" +#include "zx81rom.h" +/* -------------------------------------------------- MACROS +*/ +#define ROMLEN 0x2000 +#define ROM_SAVE 0x2fc +#define ROM_LOAD 0x347 + +#define ED_SAVE 0xf0 +#define ED_LOAD 0xf1 +#define ED_WAITKEY 0xf2 +#define ED_ENDWAITKEY 0xf3 +#define ED_PAUSE 0xf4 + +#define SLOW_TSTATES 16000 +#define FAST_TSTATES 64000 + +#define E_LINE 16404 +#define LASTK1 16421 +#define LASTK2 16422 +#define MARGIN 16424 +#define FRAMES 16436 +#define CDFLAG 16443 + +#define DFILE 0x400c + +#define PEEKW(addr) (mem[addr] | (Z80Word)mem[addr+1]<<8) + +#define POKEW(addr,val) do \ + { \ + Z80Word wa = addr; \ + Z80Word wv = val; \ + mem[wa] = wv; \ + mem[wa+1] = wv>>8; \ + } while(0) + +#define TXT_W 32 +#define TXT_H 24 + +/* -------------------------------------------------- GLOBALS +*/ +static Z80Val FRAME_TSTATES = FAST_TSTATES; + +static Z80Byte mem[0x10000]; + +static Z80Word RAMBOT; +static Z80Word RAMTOP; + +static int waitkey = FALSE; +static int started = FALSE; + +static unsigned prev_lk1; +static unsigned prev_lk2; + +static Z80 *z80; + +static Z80Byte matrix[8]; + + +/* -------------------------------------------------- ZX81 CODE +*/ +static void RomPatch(const Z80Byte patch[], Z80Word addr) +{ + int f; + + for(f = 0; patch[f] != 0xff; f++) + { + mem[addr++] = patch[f]; + } +} + + +static int EDCallback(Z80 *z80, Z80Val data) +{ + Z80Word pause; + + switch((Z80Byte)data) + { + case ED_SAVE: + if (z80->DE.w < 0x8000) + { + /* TODO: Save */ + } + + break; + + case ED_LOAD: + if (z80->DE.w < 0x8000) + { + /* TODO: Load */ + } + + mem[CDFLAG] = 0xc0; + + break; + + case ED_WAITKEY: + waitkey = TRUE; + started = TRUE; + break; + + case ED_ENDWAITKEY: + waitkey = FALSE; + break; + + case ED_PAUSE: + waitkey = TRUE; + + pause = z80->BC.w; + + while(pause-- && !(mem[CDFLAG]&1)) + { + /* TODO: Pause code */ + } + + waitkey = FALSE; + break; + } + + return TRUE; +} + + +static void ZX81HouseKeeping(void) +{ + unsigned row; + unsigned lastk1; + unsigned lastk2; + + /* British ZX81 + */ + mem[MARGIN]=55; + + /* Update FRAMES + */ + if (FRAME_TSTATES == SLOW_TSTATES) + { + Z80Word frame = PEEKW(FRAMES) & 0x7fff; + + if (frame) + { + frame--; + } + + POKEW(FRAMES, frame|0x8000); + } + + if (!started) + { + prev_lk1 = 0; + prev_lk2 = 0; + return; + } + + /* Update LASTK + */ + lastk1 = 0; + lastk2 = 0; + + for(row = 0; row < 8; row++) + { + unsigned b; + + b = (~matrix[row]&0x1f)<<1; + + if (row == 0) + { + unsigned shift; + + shift = b&2; + b &= ~2; + b |= (shift>>1); + } + + if (b) + { + if (b > 1) + { + lastk1 |= (1<<row); + } + + lastk2 |= b; + } + } + + if (lastk1 && (lastk1 != prev_lk1 || lastk2 != prev_lk2)) + { + mem[CDFLAG] |= 1; + } + else + { + mem[CDFLAG] &= ~1; + } + + mem[LASTK1] = lastk1^0xff; + mem[LASTK2] = lastk2^0xff; + + prev_lk1 = lastk1; + prev_lk2 = lastk2; +} + + +static int CheckTimers(Z80 *z80, Z80Val val) +{ + if (val >= FRAME_TSTATES) + { + Z80ResetCycles(z80, val - FRAME_TSTATES); + + if (started && ((mem[CDFLAG] & 0x80) || waitkey)) + { + FRAME_TSTATES = SLOW_TSTATES; + } + else + { + FRAME_TSTATES = FAST_TSTATES; + } + + if (z80->SP < 0x8000) + { + ZX81HouseKeeping(); + } + + return FALSE; + } + else + { + return TRUE; + } +} + + +static Z80Byte ZX81ReadMem(Z80 *z80, Z80Word addr) +{ + return mem[addr]; +} + + +static void ZX81WriteMem(Z80 *z80, Z80Word addr, Z80Byte val) +{ + if (addr >= RAMBOT && addr <= RAMTOP) + { + mem[addr] = val; + } +} + + +static Z80Byte ZX81ReadPort(Z80 *z80, Z80Word port) +{ + Z80Byte b = 0; + + switch(port & 0xff) + { + case 0xfe: + switch(port & 0xff00) + { + case 0xfe00: + b = matrix[0]; + break; + case 0xfd00: + b = matrix[1]; + break; + case 0xfb00: + b = matrix[2]; + break; + case 0xf700: + b = matrix[3]; + break; + case 0xef00: + b = matrix[4]; + break; + case 0xdf00: + b = matrix[5]; + break; + case 0xbf00: + b = matrix[6]; + break; + case 0x7f00: + b = matrix[7]; + break; + } + + /* Some code expects some of the top bits set. Whether this is a + good idea or not is unsure. + */ + b |= 0x60; + + break; + + default: + b = 0xff; + break; + } + + return b; +} + + +void ZX81WritePort(Z80 *z80, Z80Word port, Z80Byte val) +{ +} + + +static void ZX81Init(void) +{ + static const Z80Byte save[]= + { + 0xed, ED_SAVE, /* (SAVE) */ + 0xc3, 0x07, 0x02, /* JP $0207 */ + 0xff /* End of patch */ + }; + + static const Z80Byte load[]= + { + 0xed, ED_LOAD, /* (LOAD) */ + 0xc3, 0x07, 0x02, /* JP $0207 */ + 0xff /* End of patch */ + }; + + static const Z80Byte fast_hack[]= + { + 0xed, ED_WAITKEY, /* (START KEY WAIT) */ + 0xcb,0x46, /* L: bit 0,(hl) */ + 0x28,0xfc, /* jr z,L */ + 0xed, ED_ENDWAITKEY, /* (END KEY WAIT) */ + 0x00, /* nop */ + 0xff /* End of patch */ + }; + + static const Z80Byte kbd_hack[]= + { + 0x2a,0x25,0x40, /* ld hl,(LASTK) */ + 0xc9, /* ret */ + 0xff /* End of patch */ + }; + + static const Z80Byte pause_hack[]= + { + 0xed, ED_PAUSE, /* (PAUSE) */ + 0x00, /* nop */ + 0xff /* End of patch */ + }; + + int f; + + /* Load and patch ROM + */ + memcpy(mem, ZX81ROM, ROMLEN); + + RomPatch(save, ROM_SAVE); + RomPatch(load, ROM_LOAD); + RomPatch(fast_hack, 0x4ca); + RomPatch(kbd_hack, 0x2bb); + RomPatch(pause_hack, 0xf3a); + + mem[0x21c] = 0x00; + mem[0x21d] = 0x00; + + mem[0x0079] = 0; + mem[0x02ec] = 0; + + /* Lodge Z80 callbacks + */ + Z80LodgeCallback(z80, eZ80_EDHook, EDCallback); + Z80LodgeCallback(z80, eZ80_Instruction, CheckTimers); + + /* Mirror the ROM + */ + memcpy(mem + ROMLEN, mem, ROMLEN); + + /* 16K of memory + */ + RAMBOT = 0x4000; + RAMTOP = RAMBOT + 0x4000; + + for(f = RAMBOT; f <= RAMTOP; f++) + { + mem[f] = 0; + } + + /* Keyboard matrix + */ + for(f = 0; f < 8; f++) + { + matrix[f] = 0x1f; + } + + /* Fill the upper 32K with RET opcodes for ULA reads + */ + for(f = 0x8000; f < 0x10000; f++) + { + mem[f] = 0xc9; + } +} + + +/* -------------------------------------------------- UTILS +*/ static long TimeDiff(const struct timespec *start, const struct timespec *end) { @@ -35,6 +433,9 @@ static long TimeDiff(const struct timespec *start, return diff; } + +/* -------------------------------------------------- MAIN +*/ int main(void) { struct timespec start; @@ -42,6 +443,20 @@ int main(void) setlocale(LC_ALL, ""); + z80 = Z80Init(ZX81ReadMem, + ZX81WriteMem, + ZX81ReadPort, + ZX81WritePort, + ZX81ReadMem); + + if (!z80) + { + fprintf(stderr, "Failed to initialise the Z80 CPU\n"); + return EXIT_FAILURE; + } + + ZX81Init(); + initscr(); raw(); noecho(); @@ -56,6 +471,8 @@ int main(void) struct timespec pause = {0}; int ch; + Z80Exec(z80); + clear(); ch = getch(); @@ -71,7 +488,7 @@ int main(void) start = end; pause.tv_nsec = 20000000 - TimeDiff(&start, &end); - if (pause.tv_nsec > 0) + if (pause.tv_nsec > 0 && FRAME_TSTATES != FAST_TSTATES) { nanosleep(&pause, NULL); } |