/* 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 #include #include #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<=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.yHSYNC_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 */