BBC Basic in Z80 emulation on the mbed, USB serial terminal output only. LOAD and SAVE work on the local file system but there is no error signalling.

Dependencies:   mbed

Revision:
0:806c2f2a7d47
diff -r 000000000000 -r 806c2f2a7d47 main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Wed Jun 29 14:25:56 2011 +0000
@@ -0,0 +1,741 @@
+/* BBC Basic emulator on NXP mbed by Gert van der Knokke
+ * Emulation of the Z80 CPU
+ * The z80 emulator is heavily modified by me (Gert)
+ * original Copyright (C) 1994 Ian Collier.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "mbed.h"
+#include "module.h"
+#include "z80.h"
+
+#define DEBUG false
+
+// define serial port for debug
+Serial linktopc(USBTX,USBRX);
+
+
+// VDU queue
+unsigned char vdu_queue[256];   // a somewhat large queue
+unsigned char queue_bytes=0;    // vdu queue pointer
+unsigned char oldchar;          // which VDU function needs the queue
+
+volatile unsigned char flag,oldy=0;
+
+
+int background_color;   // for clear screen
+int cursor_xpos;
+int cursor_ypos;
+int last_line_processed;
+int previous_line_found;
+
+int current_background_color;
+int current_text_color;
+
+
+// prepare mbed storage for local use
+LocalFileSystem local("local");               // Create the local filesystem under the name "local"
+
+// clear the screen
+void cls() {
+    putchar(0x0c);
+}
+
+// dummy function
+void set_cursor_position() {
+}
+
+// home cursor
+void home_cursor() {
+    cursor_xpos=0x00;  // ypos
+    cursor_ypos=0x00;  // xpos
+    set_cursor_position();
+}
+
+// prepare the screen
+void init_screen() {
+    current_background_color=0;  // set initial background color
+    current_text_color=7;  // initial text color attributes
+    cls();
+    home_cursor();
+}
+
+// handle VDU calls after all bytes are in queue
+// Note! queue is working front to back (255 is first, 0 is last)
+void do_handle_queue(unsigned char code) {
+    switch (code) {
+        case 0x11:  // select text color
+            current_text_color=vdu_queue[255];
+            break;
+        case 0x16:  // Mode
+            current_text_color=7;
+            current_background_color=0;
+            init_screen();
+            home_cursor();
+            break;
+        case 0x17:  // define character n
+            break;
+        case 0x1f:  // move cursor to x,y
+            cursor_xpos=vdu_queue[254];
+            cursor_ypos=vdu_queue[255];
+            set_cursor_position();
+            break;
+    }
+}
+
+// VDU OSWRCHR
+// this routine evaluates the stream characters
+// and prepares the queue if needed
+void printchar(unsigned char ch) {
+
+    // do we need more queue bytes ?
+    if (queue_bytes) {
+        vdu_queue[queue_bytes++]=ch;
+        // all bytes in queue, now handle the call
+        if (queue_bytes==0)
+            do_handle_queue(oldchar);
+
+    } else {
+
+        switch (ch) {
+            case 0x00:  // NULL
+                break;
+            case 0x08:  // CURSOR LEFT
+                putchar(0x08);
+                if (cursor_xpos>0) {
+                    cursor_xpos--;
+                    set_cursor_position();
+                }
+                break;
+            case 0x09:  // CURSOR RIGHT
+                if (cursor_xpos<39) {
+                    cursor_xpos++;
+                    set_cursor_position();
+                }
+                break;
+            case 0x0a:  // CURSOR DOWN
+                if (cursor_ypos<23) {
+                    cursor_ypos++;
+                    set_cursor_position();
+                } else {
+                    cursor_ypos=23;
+                }
+                break;
+            case 0x0b:  // CURSOR UP
+                cursor_ypos++;
+                if (cursor_ypos>0) {
+                    cursor_ypos--;
+                    set_cursor_position();
+                } else {
+                    cursor_ypos=0;
+                }
+                break;
+            case 0x0d:  // Carriage Return
+                cursor_xpos=0;
+                putchar(0x0d);
+                putchar(0x0a);
+                set_cursor_position();
+                break;
+            case 0x0c:  // CLEAR TEXT WINDOW
+            case 0x10:  // CLEAR GRAPHIC WINDOW
+                cls();
+                cursor_xpos=0;
+                cursor_ypos=0;
+                set_cursor_position();
+                break;
+            case 0x11:    // set TEXT color
+                oldchar=ch;         // this was the code of the initial call
+                queue_bytes=256-1;      // for this call we need one more byte
+                break;
+            case 0x14:    // reset colors
+                current_text_color=7;
+                current_background_color=0;
+                break;
+            case 0x16:  // MODE select
+                oldchar=ch;
+                queue_bytes=256-1;
+                break;
+            case 0x17:  // define character n
+                break;
+            case 0x1e:
+                cursor_xpos=0;
+                cursor_ypos=0;
+                set_cursor_position();
+                break;
+            case 0x1f:              // set cursor to position X,Y
+                oldchar=ch;         // this was the code of the initial call
+                queue_bytes=256-2;      // for this call we need two more bytes
+                break;
+                // all else is put on screen literally
+            default:
+                putchar(ch);
+                break;
+        }
+    }
+}
+
+
+void init_ramtop() {
+
+
+    // set all MOS calls to illegal opcode 0xed55
+    // that code is trapped by a mbed routine
+    wrmem(OSWRCH,0xed);
+    wrmem(OSWRCH+1,0x55);
+
+    wrmem(OSWORD,0xed);
+    wrmem(OSWORD+1,0x55);
+
+    wrmem(OSBYTE,0xed);
+    wrmem(OSBYTE+1,0x55);
+
+    wrmem(OSRDCH,0xed);
+    wrmem(OSRDCH+1,0x55);
+
+    wrmem(OSNEWL,0xed);
+    wrmem(OSNEWL+1,0x55);
+
+    wrmem(OSASCI,0xed);
+    wrmem(OSASCI+1,0x55);
+
+    wrmem(OSRDCH,0xed);
+    wrmem(OSRDCH+1,0x55);
+
+    wrmem(MAINVDU,0xed);
+    wrmem(MAINVDU+1,0x55);
+
+    wrmem(GSINIT,0xed);
+    wrmem(GSINIT+1,0x55);
+
+    wrmem(GSREAD,0xed);
+    wrmem(GSREAD+1,0x55);
+
+    wrmem(OSRDRM,0xed);
+    wrmem(OSRDRM+1,0x55);
+
+    wrmem(OSEVEN,0xed);
+    wrmem(OSEVEN+1,0x55);
+
+    wrmem(OSCLI,0xed);
+    wrmem(OSCLI+1,0x55);
+
+    wrmem(OSFILE,0xed);
+    wrmem(OSFILE+1,0x55);
+
+    wrmem(CMDPTR,0xf0); // pointer to CR terminated string (auto start filename)
+    wrmem(CMDPTR+1,0x00); // it is set here to 0x00f0
+
+    wrmem(BRKV,0x00);   // break vector
+    wrmem(BRKV+1,0x00);
+
+    wrmem(FAULT,0x38);  // fault vector
+    wrmem(FAULT+1,0x00);
+}
+
+unsigned char getkey() {
+    unsigned char key=0x00;
+    if (linktopc.readable())        // get USB status
+        key=linktopc.getc();        // get ASCII key
+    return key;
+}
+
+// crude getline routine
+void mygets(int ad,int max,unsigned char minasc,unsigned char maxasc) {
+    int n=0;
+    int key=0;
+
+    bool eflag=1;
+
+    // show cursor
+    set_cursor_position();
+
+    while (eflag) {
+//        key=getkey();
+#if !DEBUG
+        // USB serial data ? then override key  (only when NOT in debug mode)
+        if (linktopc.readable())        // get USB status
+            key=linktopc.getc();        // get ASCII key code
+        else key=0;
+        // key=getchar();  // BLOCKING MODE
+#endif
+
+        if (key)    // printf("got key code: %02x\n\r",key);
+
+            // only ASCII should arrive here
+            switch (key) {
+                case 0x00:  // no key
+                    break;
+                case 0x0d:  // Enter or Return
+                    wrmem(ad+n,key);
+                    h=n;
+                    // clear carry flag
+                    anda(a); // z80 way of clearing carry...
+                    eflag=0; // terminate the loop
+                    printchar(key);
+                    printchar(0x0a);
+                    break;
+                case 0x1b:    // Escape
+                    wrmem(ad+n,key);
+                    // set carry flag
+                    f=(f&0xc4)|1|(a&0x28);
+                    eflag=0;
+                    h=n;
+                    break;
+                case 0x15:    // CTRL-U (clear input)
+                    while (n) {
+
+                        printchar(0x08);
+                        printchar(0x20);
+                        printchar(0x08);
+                        n--;
+                    }
+                    wrmem(ad,0x0d);
+                    break;
+                case 0x7f:   // DELETE
+                    if (n) {
+                        n--;
+                        printchar(0x08);    // cursor left
+                        printchar(0x20);    // delete character by printing a space
+                        printchar(0x08);    // cursor left
+                        wrmem(ad+n,0x20);   // overwrite with space
+                    }
+                    break;
+                default:    // all else
+                    // keep within maximum lenght and given ascii limits
+                    if ((key>=minasc) && (key<=maxasc) && n<max) {
+                        wrmem(ad+n,key);
+                        n++;
+                        printchar(key);
+                    }
+                    break;
+            }
+    }
+}
+
+
+// OSWORD
+void do_osword() {
+    int ad,n;
+    char buf[40];
+    time_t seconds;
+
+    // printf("OSWORD called with PC=%04x A=%02x HL=%02x%02x\n\r",pc,a,h,l);
+    switch (a) {
+
+        case 0x00:  // get a line from the keyboard/input channel
+            ad=(h<<8)+l;
+//        printf("Control block:\n\r");
+//        printf("buffer address %02x%02x\n\r",rdmem(ad+1),rdmem(ad));
+//        printf("maximum length: %d\n\r",rdmem(ad+2));
+//        printf("minimum ASCII value %d\n\r",rdmem(ad+3));
+//        printf("maximum ASCII value %d\n\r",rdmem(ad+4));
+
+            mygets((rdmem(ad+1)<<8)+rdmem(ad),rdmem(ad+2),rdmem(ad+3),rdmem(ad+4));
+
+            break;
+        case 0x01:  // read systemclock (long integer)
+            break;
+        case 0x02:  // write systemclock (long integer)
+            break;
+        case 0x07:  // emit a sound from the sound library (0-7)
+            break;
+        case 0x0e:  // read TIME$ (not present in original BBC Basic)
+            ad=hl;              // get pointer to function number
+            switch (rdmem(hl)) { // select function number
+                case 0:     // return time string
+                    seconds = time(NULL);   // get current time from mbed RTC
+                    strftime(buf,40, "%a,%d %m %Y.%H:%M:%S\r", localtime(&seconds));   // write to temporary buffer
+                    n=0;
+                    while (buf[n]) wrmem(ad++,buf[n++]);    // copy to pointer from HL
+                    break;
+                case 1:     // return BCD values of clock
+                    // On exit:
+                    //  XY?0=year (&00-&99)
+                    //  XY?1=month (&01-&12)
+                    //  XY?2=date (&01-&31)
+                    //  XY?3=day of week (&01-&07, Sun-Sat)
+                    //  XY?4=hours (&00-&23)
+                    //  XY?5=minutes (&00-&59)
+                    //  XY?6=seconds (&00-&59).
+                    //
+                    // A year value of &80-&99 represents 1980-1999, a value of
+                    // &00-&79 represents 2000-2079.
+                    //
+                    break;
+                case 2:     // Convert BCD to string.
+                    // On entry:
+                    //  XY+1..7=BCD value
+                    // On exit:
+                    // XY+1..25=CR-terminated string
+                    break;
+            }
+            break;
+        case 0x0f:  // write TIME$ (not present in original BBC Basic)
+            //        On entry:
+            // XY?0=function code
+            // XY+1.. value to use.
+            // Functions are:
+            // 8 - Set time to value in format "HH:MM:SS"
+            // 16 - Set date to value in format "Day,DD Mon Year"
+            // 24 - Set time and date to value in format
+            // "Day,DD Mon Year.HH:MM:SS"
+            n=0;
+            ad=hl;
+            // printf(" write time$ function %d\n\r",rdmem(ad));
+            ad++;
+            while (rdmem(ad)!=0x0d) buf[n++]=rdmem(ad++);  // copy timestring to buffer
+            buf[n]=0;
+            // printf("trying to set time from %s\n\r",buf);
+            break;
+    }
+}
+
+// OSBYTE
+// on 6502 A register and X,Y are used for Low,High parameters
+// Z80 uses L and H  (so X=L and H=Y <-> L=X and Y=H)
+void do_osbyte() {
+    unsigned char temp;
+
+    // printf("OSBYTE called with PC=%04x A=%02x H=%02x L=%02x\n\r",pc,a,h,l);
+    switch (a) {
+        case 0x00:      // NULL
+            l=0xff;
+            break;
+        case 0x13:      // ASCII 19
+            break;
+        case 0x7c:    // clear ESCAPE state
+            wrmem(0xff80,rdmem(0xff80)&0x7f);
+            break;
+        case 0x7d:    // set ESCAPE state
+            wrmem(0xff80,rdmem(0xff80)|0x80);
+            break;
+        case 0x7e:    // acknowledge ESCAPE state ? (CLOSE ALL!)
+            wrmem(0xff80,rdmem(0xff80)&0x7f);
+            break;
+        case 0x7f:    // read EOF on channel
+            l=0x01;
+            break;
+        case 0x80:    // read ADVAL(hl)
+            h=0;
+            l=0;
+            break;
+        case 0x81:    // read key within time limit 'hl' centiseconds
+            temp=hl;        // get timeout value (100th seconds)
+            do {
+                l=getkey();
+                wait(0.01);
+            } while ((temp--) && (!l));
+
+            if (l) {    // we got a key
+                if (l==0x1b) { // Escape ?
+                    f=(f&0xc4)|1|(a&0x28);  // set carry
+                    h=0x1b;
+                } else {
+                    anda(a); // clear carry
+                    h=0x00; // clear h
+                }
+            } else {    // timeout
+                f=(f&0xc4)|1|(a&0x28);  // set carry
+                h=0xff;                 // signal no key
+            }
+            break;                     // scan keyboard etc
+        case 0x82:
+            h=l=0;
+            break;                              // Machine high order address
+        case 0x83:
+            h=PAGE>>8;
+            l=(PAGE & 0xff);
+            break;                       // lowest available memory address (PAGE)
+        case 0x84:  // highest available memory address (HIMEM)
+            h=RAMEND>>8;
+            l=RAMEND & 0xff;
+            break;
+        case 0x85:  // read bottom of display RAM for a specific mode
+            h=0x80;
+            l=0x00;
+            break;
+        case 0x86:    // return H=VPOS, L=POS
+            h=cursor_ypos;
+            l=cursor_xpos;
+            break;
+        case 0x87:  // L=character on screen at cursor position
+            h=7;
+            l=0;
+            break;
+        case 0xda:    // read/write the number of items in the VDU queue
+            // on return H contains the new value, L the old
+            temp=queue_bytes;
+            queue_bytes=(queue_bytes & h)^l;
+            h=queue_bytes;
+            l=temp;
+            break;   // read/write the number of items in the VDU queue
+    }
+//   show_registers();
+//   wait(0.1);
+}
+
+void do_osreadchar() {
+    a=getchar();
+    // test for ESCAPE
+    if (a==27) {
+        f=(f&0xc4)|1|(a&0x28);
+        wrmem(0xff80,rdmem(0xff80)|0x80);
+    }
+}
+
+void do_osnewline() {
+    printchar(0x0a);
+    printchar(0x0d);
+    a=0x0d;
+}
+
+void do_osasci() {
+    if (a==0x0d) do_osnewline();
+    else printchar(a);
+}
+
+void do_mainvdu() {
+    printchar(a);
+}
+
+void do_gsinit() {
+    printf("GSINIT called with PC=%04x A=%02x\n\r",pc,a);
+}
+
+void do_gsread() {
+}
+
+void do_osrdrm() {
+    printf("OSRDRM called with PC=%04x A=%02x H=%02x L=%02x\n\r",pc,a,h,l);
+}
+
+void do_oseven() {
+    printf("OSEVEN called with PC=%04x A=%02x H=%02x L=%02x\n\r",pc,a,h,l);
+}
+
+void do_oscli() {
+    printf("OSCLI called with PC=%04x A=%02x H=%02x L=%02x\n\r",pc,a,h,l);
+    do {
+        a=rdmem((h<<8)+l);
+        if (!++l)h++;
+        putchar(a);
+    } while (a!=0x0d);
+}
+
+void do_osfile() {
+    char buffer[200];
+    int n=0;
+    FILE *fp;
+    // get address of control block
+    int controlblock=hl;
+    int offset=rdmem(controlblock)+(rdmem(controlblock+1)<<8);
+    // create a kludgy pointer from this offset to the filename
+    char *filename=(char *)(ram+offset-RAMSTART);
+    long load_address=rdmem(controlblock+2)+(rdmem(controlblock+3)<<8)+(rdmem(controlblock+4)<<16)+(rdmem(controlblock+5)<<24);
+    long execution_address=rdmem(controlblock+6)+(rdmem(controlblock+7)<<8)+(rdmem(controlblock+8)<<16)+(rdmem(controlblock+9)<<24);
+    long start_or_length=rdmem(controlblock+10)+(rdmem(controlblock+11)<<8)+(rdmem(controlblock+12)<<16)+(rdmem(controlblock+13)<<24);
+    long end_address_or_attributes=rdmem(controlblock+14)+(rdmem(controlblock+15)<<8)+(rdmem(controlblock+16)<<16)+(rdmem(controlblock+17)<<24);
+
+
+    // printf("OSFILE called with PC=%04x A=%02x H=%02x L=%02x\n\r",pc,a,h,l);
+
+    // for (n=0; n<=0x11; n++) printf("offset %02x value %02x\n\r",n,rdmem(controlblock+n));
+
+    n=0;
+    while (filename[n]!=0x0d) n++;
+    // overwrite 0x0d with end of string
+    filename[n]=0x00;
+
+    // now determine what to do
+    switch (a) {
+        case 0x00: // save a section of memory as a named file
+            sprintf(buffer,"/local/%s",filename);
+            // printf("Saving file %s load adr:%08lx exec adr:%08lx start/length%08lx end/attr:%08lx\n\r",filename,load_address,execution_address,start_or_length,end_address_or_attributes);
+            fp=fopen(buffer, "w");
+            if (fp) {
+                n=start_or_length;
+                while (n!=end_address_or_attributes) fputc(rdmem(n++),fp);
+                fclose(fp);
+                a=1;
+            } else {
+                // printf("could not write %s\n\r",buffer);
+                a=0;
+                pc=rdmem(0xfffa)+(rdmem(0xfffb)<<8);    // do BRK
+            }
+            break;
+        case 0x01: // write the catalogue information (only) for the named file (disc only)
+            break;
+        case 0x02: // write the load address  (only) for the named file (disc only)
+            break;
+        case 0x03: // write the execution address  (only) for the named file (disc only)
+            break;
+        case 0x04: // write the attributes  (only) for the named file (disc only)
+            break;
+        case 0x05: // read the named file's catalogue information, place file type in A (disc only)
+            break;
+        case 0x06: // delete the named file (disc only)
+            break;
+        case 0xff: // load the named file and read the named file's catalogue information
+            sprintf(buffer,"/local/%s",filename);
+            // printf("Loading file %s load adr:%08lx exec adr:%08lx start/length%08lx end/attr:%08lx\n\r",buffer,load_address,execution_address,start_or_length,end_address_or_attributes);
+            fp = fopen(buffer, "r");
+            if (fp) {
+                n=load_address;
+                while (!feof(fp)) wrmem(n++,fgetc(fp));
+                fclose(fp);
+                a=1;    // file found
+            } else {
+                // printf("Could not open %s\n",buffer);
+                a=0;
+                pc=rdmem(0xfffa)+(rdmem(0xfffb)<<8);    // do BRK
+            }
+            break;
+    }
+}
+
+void terminalgets(char *buffer) {
+    int n=0;
+    int key=0;
+    do {
+        key=getchar();
+        buffer[n]=key;
+        putchar(key);
+        if (n<16) n++;
+    } while (key != 0x0d);
+    buffer[n]=0;
+}
+
+
+// here the OS calls are recognized and
+// diverted to the mbed routines.
+// this needs to be replaced by native Z80 code
+void do_mos() {
+    //  printf("Entering MOS emulation with PC %04x\n\r",pc);
+
+    // compensate pc for length of ED55 opcode
+    switch (pc-2) {
+        case OSBYTE:
+            do_osbyte();
+            break;
+        case OSWORD:
+            do_osword();
+            break;
+        case OSWRCH:
+            printchar(a);
+            break;
+        case OSRDCH:
+            do_osreadchar();
+            break;
+        case OSNEWL:
+            do_osnewline();
+            break;
+        case OSASCI:
+            do_osasci();
+            break;
+        case MAINVDU:
+            do_mainvdu();
+            break;
+        case GSINIT:
+            do_gsinit();
+            break;
+        case GSREAD:
+            do_gsread();
+            break;
+        case OSRDRM:
+            do_osrdrm();
+            break;
+        case OSEVEN:
+            do_oseven();
+            break;
+        case OSCLI:
+            do_oscli();
+            break;
+        case OSFILE:
+            do_osfile();
+            break;
+    }
+}
+
+void listdir(void) {
+    DIR *d;
+    struct dirent *p;
+
+    d = opendir("/sd");
+    if (d != NULL) {
+        while ((p = readdir(d)) != NULL) {
+            printf(" - %s\r\n", p->d_name);
+        }
+    } else {
+        printf("Could not open directory!\n");
+    }
+    closedir(d);
+}
+
+
+
+
+
+// ================================================================
+//  main loop
+// ================================================================
+int  main() {
+
+    // serial port on at 115200 baud
+    linktopc.baud(115200);
+    setbuf(stdout, NULL); // no buffering for this filehandle
+
+    // reset all Z80 registers to some initial values
+    a=f=b=c=d=e=h=l=a1=f1=b1=c1=d1=e1=h1=l1=i=iff1=iff2=im=r=0;
+    ixoriy=new_ixoriy=0;
+    ix=iy=sp=pc=0;
+
+    queue_bytes=0;
+
+    // init MOS vectors
+    init_ramtop();
+
+    init_screen();
+    home_cursor();
+
+    // endless loop
+    while (1) {
+        //    wait(0.01);
+        r++;
+        // this is some optimization for the IX and IY opcodes (DD/FD)
+        ixoriy=new_ixoriy;
+        new_ixoriy=0;
+
+        // fetch opcode and execute, the include does all the heavy decoding
+        switch (fetch(pc++)) {
+#include "z80ops.h"
+        }
+
+        // next is the interrupt emulator (only IM1 mode)
+        // interrupt pending? if new_ixoriy is set or iff1==0 don't do interrupt yet (continue DD/FD opcode)
+        //    if (intpend && !new_ixoriy && iff1) {
+        // are we HALT-ed ? then resume
+        //      if (fetch(pc)==0x76) pc++;
+
+        // block further interrupts for now
+        //    iff1=0;
+
+        // do the call to 00x38
+        //   push2(pc);   // save old pc
+        //  pc=0x0038;   // setup new pc
+        //  intpend=0;   // release interrupt pending flag;
+        //   } // if intpend
+    } // while
+}
\ No newline at end of file