summaryrefslogtreecommitdiff
path: root/src/spec.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/spec.c')
-rw-r--r--src/spec.c673
1 files changed, 673 insertions, 0 deletions
diff --git a/src/spec.c b/src/spec.c
new file mode 100644
index 0000000..51006df
--- /dev/null
+++ b/src/spec.c
@@ -0,0 +1,673 @@
+/*
+
+ espec - Sinclair Spectrum emulator
+
+ Copyright (C) 2003 Ian Cowburn (ianc@noddybox.demon.co.uk)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ -------------------------------------------------------------------------
+
+ Provides the emulation for the SPEC
+
+*/
+static const char ident[]="$Id$";
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "spec.h"
+#include "gfx.h"
+#include "gui.h"
+#include "config.h"
+#include "exit.h"
+
+static const char ident_h[]=ESPEC_SPECH;
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+
+/* ---------------------------------------- STATICS
+*/
+static const int ROMLEN=0x2000;
+static const int ROM_SAVE=0x2fc;
+static const int ROM_LOAD=0x347;
+
+/* No of cycles in each 64us HSYNC (hopefully)
+*/
+static const int HSYNC_PERIOD=321;
+
+/* The SPEC screen
+*/
+static const int SCR_W=256;
+static const int SCR_H=192;
+static const int TXT_W=32;
+static const int TXT_H=24;
+
+/* These assume a 320x200 screen
+*/
+static const int OFF_X=(320-256)/2;
+static const int OFF_Y=(200-192)/2;
+
+static Z80Byte mem[0x10000];
+
+static Z80Word RAMBOT=0;
+static Z80Word RAMTOP=0;
+static Z80Word RAMLEN=0;
+
+/* Counter used when triggering the interrupts for the display
+*/
+static int nmigen=FALSE;
+static int hsync=FALSE;
+
+/* The ULA
+*/
+static struct
+{
+ int x;
+ int y;
+ int c;
+ int release;
+} ULA;
+
+/* GFX vars
+*/
+static Uint32 white;
+static Uint32 black;
+
+
+/* The keyboard
+*/
+static Z80Byte matrix[8];
+
+typedef struct
+{
+ SDLKey key;
+ int m1,b1,m2,b2;
+} MatrixMap;
+
+#define KY1(m,b) m,1<<b,-1,-1
+#define KY2(m1,b1,m2,b2) m1,1<<b1,m2,1<<b2
+
+static const MatrixMap keymap[]=
+{
+ {SDLK_1, KY1(3,0)},
+ {SDLK_2, KY1(3,1)},
+ {SDLK_3, KY1(3,2)},
+ {SDLK_4, KY1(3,3)},
+ {SDLK_5, KY1(3,4)},
+ {SDLK_6, KY1(4,4)},
+ {SDLK_7, KY1(4,3)},
+ {SDLK_8, KY1(4,2)},
+ {SDLK_9, KY1(4,1)},
+ {SDLK_0, KY1(4,0)},
+
+ {SDLK_a, KY1(1,0)},
+ {SDLK_b, KY1(7,4)},
+ {SDLK_c, KY1(0,3)},
+ {SDLK_d, KY1(1,2)},
+ {SDLK_e, KY1(2,2)},
+ {SDLK_f, KY1(1,3)},
+ {SDLK_g, KY1(1,4)},
+ {SDLK_h, KY1(6,4)},
+ {SDLK_i, KY1(5,2)},
+ {SDLK_j, KY1(6,3)},
+ {SDLK_k, KY1(6,2)},
+ {SDLK_l, KY1(6,1)},
+ {SDLK_m, KY1(7,2)},
+ {SDLK_n, KY1(7,3)},
+ {SDLK_o, KY1(5,1)},
+ {SDLK_p, KY1(5,0)},
+ {SDLK_q, KY1(2,0)},
+ {SDLK_r, KY1(2,3)},
+ {SDLK_s, KY1(1,1)},
+ {SDLK_t, KY1(2,4)},
+ {SDLK_u, KY1(5,3)},
+ {SDLK_v, KY1(0,4)},
+ {SDLK_w, KY1(2,1)},
+ {SDLK_x, KY1(0,2)},
+ {SDLK_y, KY1(5,4)},
+ {SDLK_z, KY1(0,1)},
+
+ {SDLK_RETURN, KY1(6,0)},
+ {SDLK_SPACE, KY1(7,0)},
+
+ {SDLK_COMMA, KY1(7,1)}, /* In the right place... */
+ {SDLK_PERIOD, KY1(7,1)}, /* ...or the right key... */
+
+ {SDLK_BACKSPACE, KY2(0,0,4,0)},
+ {SDLK_DELETE, KY2(0,0,4,0)},
+ {SDLK_UP, KY2(0,0,4,3)},
+ {SDLK_DOWN, KY2(0,0,4,4)},
+ {SDLK_LEFT, KY2(0,0,3,4)},
+ {SDLK_RIGHT, KY2(0,0,4,2)},
+
+ {SDLK_RSHIFT, KY1(0,0)},
+ {SDLK_LSHIFT, KY1(0,0)},
+
+ {SDLK_UNKNOWN, 0,0,0,0},
+};
+
+
+/* ---------------------------------------- PRIVATE FUNCTIONS
+*/
+static void RomPatch(void)
+{
+ static const Z80Byte save[]=
+ {
+ 0xed, 0xf0, /* ED F0 illegal op */
+ 0xc3, 0x08, 0x02, /* JP $0207 */
+ 0xff /* End of patch */
+ };
+
+ static const Z80Byte load[]=
+ {
+ 0xed, 0xf1, /* ED F0 illegal op */
+ 0xc3, 0x08, 0x02, /* JP $0207 */
+ 0xff /* End of patch */
+ };
+
+ int f;
+
+ for(f=0;save[f]!=0xff;f++)
+ mem[ROM_SAVE+f]=save[f];
+
+ for(f=0;load[f]!=0xff;f++)
+ mem[ROM_LOAD+f]=load[f];
+}
+
+
+static char ToASCII(Z80Byte b)
+{
+ if (b==0) /* SPACE */
+ return ' ';
+
+ if (b==22) /* Dash (-) */
+ return '-';
+
+ if (b>=28 && b<=37) /* 0-9 */
+ return '0'+b-28;
+
+ if (b>=38 && b<=63) /* A-Z */
+ return 'a'+b-38;
+
+ return 0;
+}
+
+
+static const char *ConvertFilename(Z80Word addr)
+{
+ static char buff[FILENAME_MAX];
+ char *p;
+
+ p=buff;
+ *p=0;
+
+ if (addr>0x8000)
+ return buff;
+
+ do
+ {
+ char c=ToASCII(mem[addr]&0x7f);
+
+ if (c)
+ *p++=c;
+
+ } while(mem[addr++]<0x80);
+
+ *p=0;
+
+ return buff;
+}
+
+
+static void LoadTape(Z80State *state)
+{
+ const char *p=ConvertFilename(state->DE);
+ char path[FILENAME_MAX];
+ FILE *fp;
+ Z80Word addr;
+ int c;
+
+ if (strlen(p)==0)
+ {
+ GUIMessage("ERROR","Can't load empty filename");
+ return;
+ }
+
+ strcpy(path,SConfig(CONF_TAPEDIR));
+ strcat(path,"/");
+ strcat(path,p);
+ strcat(path,".p");
+
+ if (!(fp=fopen(path,"rb")))
+ {
+ GUIMessage("ERROR","Can't load file:\n%s",path);
+ return;
+ }
+
+ addr=0x4009;
+
+ while((c=getc(fp))!=EOF)
+ {
+ if (addr>=0x4000)
+ mem[addr]=(Z80Byte)c;
+
+ addr++;
+ }
+
+ fclose(fp);
+}
+
+
+static void SaveTape(Z80State *state)
+{
+ const char *p=ConvertFilename(state->DE);
+ char path[FILENAME_MAX];
+ FILE *fp;
+ Z80Word start;
+ Z80Word end;
+
+ if (strlen(p)==0)
+ {
+ GUIMessage("ERROR","Can't save empty filename");
+ return;
+ }
+
+ strcpy(path,SConfig(CONF_TAPEDIR));
+ strcat(path,"/");
+ strcat(path,p);
+ strcat(path,".p");
+
+ if (!(fp=fopen(path,"wb")))
+ {
+ GUIMessage("ERROR","Can't write file:\n%s",path);
+ return;
+ }
+
+ start=0x4009;
+ end=(Z80Word)mem[0x4014]|(Z80Word)mem[0x4015]<<8;
+
+ while(start<=end)
+ putc(mem[start++],fp);
+
+ fclose(fp);
+}
+
+
+static int EDCallback(Z80 *z80, Z80Val data)
+{
+ Z80State state;
+
+ Z80GetState(z80,&state);
+
+ switch((Z80Byte)data)
+ {
+ case 0xf0:
+ SaveTape(&state);
+ break;
+
+ case 0xf1:
+ LoadTape(&state);
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+
+static void ULA_Video_Shifter(Z80 *z80, Z80Byte val)
+{
+ Z80State state;
+ Z80Word base;
+ int x,y;
+ int inv;
+ int b;
+
+ Z80GetState(z80,&state);
+
+ /* Extra check due to out dodgy ULA emulation
+ */
+ if (ULA.y>=0 && ULA.y<SCR_H)
+ {
+ Uint32 fg,bg;
+
+ /* Position on screen corresponding to ULA
+ */
+ x=OFF_X+ULA.x*8;
+ y=OFF_Y+ULA.y;
+
+ /* Get ULA invert state and clear to ULA 6-but code
+ */
+ inv=val&0x80;
+ val&=0x3f;
+
+ base=((Z80Word)state.I<<8)|(val<<3)|ULA.c;
+
+ if (inv)
+ {
+ fg=white;
+ bg=black;
+ }
+ else
+ {
+ fg=black;
+ bg=white;
+ }
+
+ for(b=0;b<8;b++)
+ {
+ if (mem[base]&(1<<(7-b)))
+ GFXPlot(x+b,y,fg);
+ else
+ GFXPlot(x+b,y,bg);
+ }
+ }
+
+ ULA.x=(ULA.x+1)&0x1f;
+
+ if (ULA.x==0)
+ Z80Interrupt(z80,0xff);
+}
+
+
+static int CheckTimers(Z80 *z80, Z80Val val)
+{
+ if (val>HSYNC_PERIOD)
+ {
+ Z80ResetCycles(z80,0);
+
+ if (nmigen)
+ {
+ Z80NMI(z80,0xff);
+ printf("NMIGEN\n");
+ }
+ else if (hsync)
+ {
+ printf("HSYNC\n");
+ if (ULA.release)
+ {
+ /* ULA.release=FALSE; */
+ ULA.c=(ULA.c+1)&7;
+ ULA.y++;
+ ULA.x=0;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+
+/* ---------------------------------------- EXPORTED INTERFACES
+*/
+void SPECInit(Z80 *z80)
+{
+ FILE *fp;
+ Z80Word f;
+
+ if (!(fp=fopen(SConfig(CONF_ROMFILE),"rb")))
+ {
+ GUIMessage("ERROR","Failed to open SPEC ROM\n%s",SConfig(CONF_ROMFILE));
+ Exit("");
+ }
+
+ if (fread(mem,1,ROMLEN,fp)!=ROMLEN)
+ {
+ fclose(fp);
+ GUIMessage("ERROR","ROM file must be %d bytes long\n",ROMLEN);
+ Exit("");
+ }
+
+ /* Patch the ROM
+ */
+ RomPatch();
+ Z80LodgeCallback(z80,Z80_EDHook,EDCallback);
+ Z80LodgeCallback(z80,Z80_Fetch,CheckTimers);
+
+ /* Mirror the ROM
+ */
+ memcpy(mem+ROMLEN,mem,ROMLEN);
+
+ RAMBOT=0x4000;
+
+ /* Memory size (1 or 16K)
+ */
+ if (IConfig(CONF_MEMSIZE)==16)
+ RAMLEN=0x2000;
+ else
+ RAMLEN=0x400;
+
+ RAMTOP=RAMBOT+RAMLEN;
+
+ for(f=RAMBOT;f<=RAMTOP;f++)
+ mem[f]=0;
+
+ for(f=0;f<8;f++)
+ matrix[f]=0x1f;
+
+ white=GFXRGB(230,230,230);
+ black=GFXRGB(0,0,0);
+
+ nmigen=FALSE;
+ hsync=FALSE;
+
+ GFXStartFrame();
+}
+
+
+void SPECKeyEvent(SDL_Event *e)
+{
+ const MatrixMap *m;
+
+ m=keymap;
+
+ while(m->key!=SDLK_UNKNOWN)
+ {
+ if (e->key.keysym.sym==m->key)
+ {
+ if (e->key.state==SDL_PRESSED)
+ {
+ matrix[m->m1]&=~m->b1;
+
+ if (m->m2!=-1)
+ matrix[m->m2]&=~m->b2;
+ }
+ else
+ {
+ matrix[m->m1]|=m->b1;
+
+ if (m->m2!=-1)
+ matrix[m->m2]|=m->b2;
+ }
+ }
+
+ m++;
+ }
+}
+
+
+Z80Byte SPECReadMem(Z80 *z80, Z80Word addr)
+{
+ /* Memory reads above 32K invoke the ULA
+ */
+ if (addr>0x7fff)
+ {
+ Z80Byte b;
+
+ /* B6 of R is tied to the IRQ line (only when HSYNC active?)
+ if ((HSYNC)&&(!(z80->R&0x40)))
+ z80->IRQ=TRUE;
+ */
+
+ b=mem[addr&0x7fff];
+
+ /* If bit 6 of the opcode is set the opcode is sent as is to the
+ Z80. If it's not, the byte is interretted by the ULA.
+ */
+ if (b&0x40)
+ {
+ ULA_Video_Shifter(z80,0);
+ }
+ else
+ {
+ ULA_Video_Shifter(z80,b);
+ b=0;
+ }
+
+ return b;
+ }
+ else
+ return mem[addr&0x7fff];
+
+}
+
+
+void SPECWriteMem(Z80 *z80, Z80Word addr, Z80Byte val)
+{
+ addr=addr&0x7fff;
+
+ if (addr>=RAMBOT && addr<=RAMTOP)
+ mem[addr]=val;
+}
+
+
+Z80Byte SPECReadPort(Z80 *z80, Z80Word port)
+{
+ Z80Byte b=0;
+
+ printf("IN %4.4x\n",port);
+
+ switch(port&0xff)
+ {
+ case 0xfe: /* ULA */
+ /* Key matrix
+ */
+ 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;
+ }
+
+ /* Turn off HSYNC if NMI generator is OFF and redraw screen to
+ match output ULA has since sent out.
+ */
+ if (!nmigen && hsync)
+ {
+ hsync=FALSE;
+
+ GFXEndFrame(TRUE);
+ GFXClear(white);
+
+ ULA.x=0;
+ ULA.y=-1;
+ ULA.c=7;
+ ULA.release=FALSE;
+
+ GFXStartFrame();
+ }
+ else
+ {
+ /* Reset and hold ULA counter
+ */
+ ULA.c=0;
+ ULA.release=FALSE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return b;
+}
+
+
+void SPECWritePort(Z80 *z80, Z80Word port, Z80Byte val)
+{
+ printf("OUT %4.4x\n",port);
+
+ /* Any port write releases the ULA line counter
+ */
+ ULA.release=TRUE;
+
+ switch(port&0xff)
+ {
+ case 0xfd: /* NMI generator OFF */
+ nmigen=FALSE;
+ break;
+
+ case 0xfe: /* NMI generator ON */
+ nmigen=TRUE;
+ break;
+
+ case 0xff: /* HSYNC generator ON */
+ hsync=TRUE;
+ Z80ResetCycles(z80,0);
+ break;
+ }
+}
+
+
+Z80Byte SPECReadForDisassem(Z80 *z80, Z80Word addr)
+{
+ return mem[addr&0x7fff];
+}
+
+
+const char *SPECInfo(Z80 *z80)
+{
+ static char buff[80];
+
+ sprintf(buff,"NMIGEN: %s HSYNC: %s",
+ nmigen ? "ON":"OFF",
+ hsync ? "ON":"OFF");
+
+ return buff;
+}
+
+
+/* END OF FILE */