/* GEMMA - Z80 testbed Copyright (C) 2006 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 ------------------------------------------------------------------------- */ static const char id[]="$Id$"; #include #include #include #include #include #include "gemma.h" #include "expr.h" #include "gtkutil.h" #include "support.h" #define HI(w) (((w)&0xff00)>>8) #define LO(w) ((w)&0xff) #define MK(h,l) (((Z80Word)(h)<<8)|(l)) /* ---------------------------------------- GLOBALS */ static Z80 *z80; static Z80Byte mem[0x10000]; static int memctl[256]; static sig_atomic_t stop=FALSE; static Z80Label *label=NULL; static GtkWidget *run_button; static GtkWidget *step_button; static GtkWidget *step_over_button; static GtkWidget *stop_button; static GtkWidget *run_until_button; static GtkWidget *breakpoint_text; static GtkWidget *top_window; static GtkWidget *regview; static GtkWidget *memview; static GtkWidget *viewreg; static GtkWidget *logview; static GtkWidget *assemview; static GtkWidget *memview_combo; static GtkWidget *memview_check; #define RUNNING(t) gtk_widget_set_sensitive(run_button,!t); \ gtk_widget_set_sensitive(step_button,!t); \ gtk_widget_set_sensitive(step_over_button,!t); \ gtk_widget_set_sensitive(run_until_button,!t); \ gtk_widget_set_sensitive(stop_button,t); /* ---------------------------------------- PROTOS */ static void Log(const char *format, ...); static Z80Word Address(const char *p); static void *Malloc(size_t len); static void *Realloc(void *p, size_t len); /* ---------------------------------------- PC BUFFER */ #define PC_BUFFER 100 static Z80Word pc_buffer[PC_BUFFER]={0}; static int pc_head=0; static Z80Word Get_PC(int back) { return pc_buffer[(pc_head+back+1)%PC_BUFFER]; } static void Step(void) { pc_buffer[pc_head]=Z80GetPC(z80); if (pc_head) pc_head--; else pc_head=PC_BUFFER-1; Z80SingleStep(z80); } /* ---------------------------------------- DYNAMIC STRINGS */ typedef struct { size_t len; char *text; } *DString; #define BLOCKSIZE 512 DString DSInit(void) { DString ds; ds=Malloc(sizeof *ds); ds->len=0; ds->text=Malloc(BLOCKSIZE); ds->text[0]=0; return ds; } void DSFree(DString ds) { if (ds) { free(ds->text); free(ds); } } DString DSAddChar(DString to, char c) { if (((to->len+2)%BLOCKSIZE)==0) { to->text=Realloc(to->text,(((to->len+2)/BLOCKSIZE)+1)*BLOCKSIZE); } to->text[to->len++]=c; to->text[to->len]=0; return to; } DString DSAdd(DString to, const char *from) { to->len+=strlen(from); to->text=Realloc(to->text,(((to->len+1)/BLOCKSIZE)+1)*BLOCKSIZE); strcat(to->text,from); return to; } /* ---------------------------------------- PRIVATE FUNCTIONS */ static void Log(const char *format, ...) { static char buff[4096]; va_list va; va_start(va,format); vsprintf(buff,format,va); va_end(va); AppendText(logview,buff); } static int StrEq(const char *a, const char *b) { while(*a && *b && tolower(*a)==tolower(*b)) { a++; b++; } if (*a || *b) return FALSE; else return TRUE; } static int Expand(void *client, const char *p, long *res) { Z80State s; int ok=TRUE; Z80GetState(z80,&s); if (StrEq(p,"AF")) *res=s.AF; else if (StrEq(p,"BC")) *res=s.BC; else if (StrEq(p,"DE")) *res=s.DE; else if (StrEq(p,"HL")) *res=s.HL; else if (StrEq(p,"IX")) *res=s.IX; else if (StrEq(p,"IY")) *res=s.IY; else if (StrEq(p,"SP")) *res=s.SP; else if (StrEq(p,"PC")) *res=s.PC; else if (StrEq(p,"A")) *res=HI(s.AF); else if (StrEq(p,"F")) *res=LO(s.AF); else if (StrEq(p,"B")) *res=HI(s.BC); else if (StrEq(p,"C")) *res=LO(s.BC); else if (StrEq(p,"D")) *res=HI(s.DE); else if (StrEq(p,"E")) *res=LO(s.DE); else if (StrEq(p,"H")) *res=HI(s.HL); else if (StrEq(p,"L")) *res=LO(s.HL); else if (StrEq(p,"AF_")) *res=s.AF_; else if (StrEq(p,"BC_")) *res=s.BC_; else if (StrEq(p,"DE_")) *res=s.DE_; else if (StrEq(p,"HL_")) *res=s.HL_; else if (StrEq(p,"A_")) *res=HI(s.AF_); else if (StrEq(p,"F_")) *res=LO(s.AF_); else if (StrEq(p,"B_")) *res=HI(s.BC_); else if (StrEq(p,"C_")) *res=LO(s.BC_); else if (StrEq(p,"D_")) *res=HI(s.DE_); else if (StrEq(p,"E_")) *res=LO(s.DE_); else if (StrEq(p,"H_")) *res=HI(s.HL_); else if (StrEq(p,"L_")) *res=LO(s.HL_); else if (StrEq(p,"IM")) *res=s.IM; else if (StrEq(p,"R")) *res=s.R; else if (StrEq(p,"IFF1")) *res=s.IFF1; else if (StrEq(p,"IFF2")) *res=s.IFF2; else if (p[0]=='@') { Z80Word n; n=Address(p+1); *res=mem[n]; } else if (p[0]=='#') { Z80Word n; n=Address(p+1); *res=MK(mem[n+1],mem[n]); } else /* Check for labels */ { int f; ok=FALSE; for(f=0;label && label[f].label;f++) { if (StrEq(p,label[f].label)) { *res=label[f].address; ok=TRUE; break; } } } return ok; } static Z80Word Address(const char *p) { long e=0; if (!ExprEval(p,&e,Expand,z80)) Log("%s\n",ExprError()); return (Z80Word)e; } static void *Malloc(size_t len) { void *n; n=malloc(len); if (!n) { fprintf(stderr,"malloc failed\n"); exit(EXIT_FAILURE); } return n; } static void *Realloc(void *p, size_t len) { void *n; n=realloc(p,len); if (!n) { fprintf(stderr,"realloc failed\n"); exit(EXIT_FAILURE); } return n; } static char *StrCopy(const char *p) { return strcpy(Malloc(strlen(p)+1),p); } static const int ToHex(char c) { c=toupper(c); if (c>='0' && c<='9') return c-'0'; if (c>='A' && c<='F') return c-'A'+10; return 0; } static void FlagString(DString s, Z80Byte flag, Z80Byte last) { static char *c[]={"S","Z","5","H","3","P","N","C"}; int f; DSAdd(s,"Flags: "); for(f=0;f<8;f++) { int b=1<<(7-f); if ((last&b)!=(flag&b)) { DSAdd(s,""); } if (flag&b) { DSAdd(s,c[f]); } else { DSAdd(s,"-"); } if ((last&b)!=(flag&b)) { DSAdd(s,""); } } } static void DisplayState(DString s, const char *label, int width, Z80Val val, Z80Val last) { static char buff[128]; sprintf(buff,"%s: %s%*.*lX", label, (last==val) ? "" : "", width,width, val); DSAdd(s,buff); } /* ---------------------------------------- MEMORY */ static Z80Byte ReadPort(Z80 *z80, Z80Word addr) { Z80Byte b=0xff; Z80Word ptr; Z80State s; char expr[1024]; char *p; Z80GetState(z80,&s); switch(addr&0xff) { case 0x80: ptr=MK(mem[1],mem[0]); p=expr; while(mem[ptr]) *p++=mem[ptr++]; *p=0; b=(Z80Byte)Address(expr); /* Log("%s -> %u\n",expr,b); */ break; case 0x81: ptr=s.DE; p=expr; while(mem[ptr]) *p++=mem[ptr++]; *p=0; b=(Z80Byte)Address(expr); break; default: Log("Read from port 0x%4.4x\n",(int)addr); break; } return b; } static void WritePort(Z80 *z80, Z80Word addr, Z80Byte val) { Z80State s; DString ds; ds=DSInit(); Z80GetState(z80,&s); switch(addr&0xff) { case 0x80: DSAddChar(ds,val); break; case 0x81: DSAdd(ds,"Stop requested via OUT(0x81)\n"); stop=TRUE; break; case 0x82: while(mem[s.DE]!='$') { if (isspace(mem[s.DE]) || isprint(mem[s.DE])) { DSAddChar(ds,mem[s.DE]); } s.DE++; } break; case 0x83: while(mem[s.DE]) { DSAddChar(ds,mem[s.DE]); s.DE++; } break; default: Log("Wrote 0x%2.2x to port 0x%4.4x\n",(int)val,(int)addr); return; } Log(ds->text); DSFree(ds); } static int Halt(Z80 *z80, Z80Val v) { Log("**** Processor halted\n"); DialogOK("Processor halted\n"); stop=TRUE; return FALSE; } /* ---------------------------------------- EXPORTED FUNCTIONS */ void GEMMA_UpdateDisplay(GEMMA_View view) { Z80State reg; Z80GetState(z80,®); if (view&UPDATE_ASSEM_VIEW) { Z80Word pc; DString ds; char buff[10]; int f; ds=DSInit(); DSAdd(ds,""); for(f=10;f>=0;f--) { pc=Get_PC(f); sprintf(buff,"%4.4X: ",pc); DSAdd(ds,buff); DSAdd(ds,Z80Disassemble(z80,&pc)); DSAddChar(ds,'\n'); } pc=Z80GetPC(z80); for(f=0;f<12;f++) { if (f==0) { DSAdd(ds,""); } sprintf(buff,"%4.4X: ",pc); DSAdd(ds,buff); DSAdd(ds,Z80Disassemble(z80,&pc)); if (f==0) { DSAdd(ds,""); } DSAddChar(ds,'\n'); } DSAdd(ds,""); gtk_label_set_markup(GTK_LABEL(assemview),ds->text); DSFree(ds); } if (view&UPDATE_REG_VIEW) { static int reg_once=FALSE; static Z80State last_reg; DString ds; ds=DSInit(); if (!reg_once) { last_reg=reg; reg_once=TRUE; } DSAdd(ds,""); DisplayState(ds,"A ",2,HI(reg.AF),HI(last_reg.AF)); DSAdd(ds," "); FlagString(ds,LO(reg.AF),LO(last_reg.AF)); DSAdd(ds,"\n"); DisplayState(ds,"BC ",4,reg.BC,last_reg.BC); DSAdd(ds," "); DisplayState(ds,"DE ",4,reg.DE,last_reg.DE); DSAdd(ds," "); DisplayState(ds,"HL ",4,reg.HL,last_reg.HL); DSAdd(ds,"\n"); DisplayState(ds,"SP ",4,reg.SP,last_reg.SP); DSAdd(ds," "); DisplayState(ds,"IX ",4,reg.IX,last_reg.IX); DSAdd(ds," "); DisplayState(ds,"IY ",4,reg.IY,last_reg.IY); DSAdd(ds,"\n\n"); DisplayState(ds,"A' ",2,HI(reg.AF_),HI(last_reg.AF_)); DSAdd(ds," "); FlagString(ds,LO(reg.AF_),LO(last_reg.AF_)); DSAdd(ds,"\n"); DisplayState(ds,"BC'",4,reg.BC_,last_reg.BC_); DSAdd(ds," "); DisplayState(ds,"DE'",4,reg.DE_,last_reg.DE_); DSAdd(ds," "); DisplayState(ds,"HL'",4,reg.HL_,last_reg.HL_); DSAdd(ds,"\n\n"); DisplayState(ds,"PC ",4,reg.PC,last_reg.PC); DSAdd(ds," "); DisplayState(ds,"CYCLE",8,reg.cycle,last_reg.cycle); DSAdd(ds,"\n\n"); DisplayState(ds,"R ",2,reg.R,last_reg.R); DSAdd(ds," "); DisplayState(ds,"IFF1",1,reg.IFF1,last_reg.IFF1); DSAdd(ds," "); DisplayState(ds,"IFF2",1,reg.IFF2,last_reg.IFF2); DSAdd(ds,"\n"); DisplayState(ds,"IM ",1,reg.IM,last_reg.IM); DSAdd(ds," "); DisplayState(ds,"I",1,reg.I,last_reg.I); DSAdd(ds,"\n"); DSAdd(ds,""); gtk_label_set_markup(GTK_LABEL(regview),ds->text); DSFree(ds); last_reg=reg; } if (view&UPDATE_MEM_VIEW) { Z80Word addr; Z80Word orig; DString ds; int f; gboolean as_words; ds=DSInit(); as_words=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(memview_check)); switch(gtk_combo_box_get_active(GTK_COMBO_BOX(memview_combo))) { case 0: addr=reg.HL; break; case 1: addr=reg.SP; as_words=TRUE; break; case 2: addr=reg.IX; break; case 3: addr=reg.IY; break; case 4: addr=reg.BC; break; case 5: addr=reg.DE; break; default: addr=reg.PC; break; } DSAdd(ds,""); orig=addr; addr-=6*8; for(f=0;f<13;f++) { DString asc; char buff[10]; Z80Word off; asc=DSInit(); sprintf(buff,"%4.4X: ",addr); DSAdd(ds,buff); for(off=0;off<8;off++) { gchar *p; p=g_markup_printf_escaped ("%c",isprint(mem[addr+off]) ? mem[addr+off] : '.'); DSAdd(asc,p); g_free(p); } for(off=0;off<8;off+=(as_words ? 2:1)) { Z80Word a; a=addr+off; if (a==orig) { DSAdd(ds,""); } if (as_words) { sprintf(buff,"%4.4X ",MK(mem[a+1],mem[a])); } else { sprintf(buff,"%2.2X ",mem[a]); } DSAdd(ds,buff); if (a==orig) { DSAdd(ds,""); } } DSAdd(ds," "); DSAdd(ds,asc->text); DSAdd(ds,"\n"); DSFree(asc); addr+=8; } DSAdd(ds,""); gtk_label_set_markup(GTK_LABEL(memview),ds->text); DSFree(ds); } } void GEMMA_LoadHEX(const char *path) { FILE *fp; char buff[1024]; int done=FALSE; if (!(fp=fopen(path,"r"))) { Log("Failed to open %s\n",path); return; } while(!done) { if (!fgets(buff,sizeof buff,fp)) { Log("Missing EOF record\n"); done=TRUE; } if (!done && buff[0]!=':') { Log("Invalid Intel HEX file\n"); done=TRUE; } if (!done && buff[8]=='1') { Log("\n"); done=TRUE; } if (!done) { Z80Word addr; int len; int f; len=ToHex(buff[1])<<4|ToHex(buff[2]); addr=ToHex(buff[3])<<12|ToHex(buff[4])<<8| ToHex(buff[5])<<4|ToHex(buff[6]); Log("Segment of 0x%2.2x bytes at address 0x%4.4x\n",len,addr); for(f=0;f