summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/czx81.c419
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);
}