Add 8086 emulator (alternate backend only; based on virtualxt) and MSBASIC test
authorNick Downing <nick@ndcode.org>
Sun, 31 Jul 2022 14:10:26 +0000 (00:10 +1000)
committerNick Downing <nick@ndcode.org>
Sun, 31 Jul 2022 14:10:26 +0000 (00:10 +1000)
.gitignore
.gitmodules
Makefile
emu_8086.c [new file with mode: 0644]
entry_point.py
msbasic.com [new file with mode: 0644]
msbasic_test.txt [new file with mode: 0644]
virtualxt [new submodule]

index b1823ec..b0a355b 100644 (file)
@@ -12,5 +12,7 @@
 /emu_6800_alt
 /emu_6809
 /emu_6809_alt
+/emu_8086
+/emu_8086_alt
 /emu_z80
 /emu_z80_alt
index 6329dd7..082b493 100644 (file)
@@ -19,3 +19,6 @@
 [submodule "VCC"]
        path = VCC
        url = https://github.com/nickd4/VCC.git
+[submodule "virtualxt"]
+       path = virtualxt
+       url = https://github.com/andreas-jonsson/virtualxt.git
index 0ecd5e5..589d733 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,8 @@ CFLAGS=-g -Og -Wall -Wno-attributes -Wno-unused-function
 ALT_65C02_CFLAGS=-DALT_BACKEND=1 -DVR_6502_EMU_STATIC=1
 ALT_6800_CFLAGS=-DALT_BACKEND=1 -DUSE_PROTOTYPES -DM6800 -Isim68xx/inc/arch/m6800 -Isim68xx/inc/arch/m68xx -Isim68xx/inc/base
 ALT_6809_CFLAGS=-DALT_BACKEND=1 -IVCC
+ALT_8086_CFLAGS=-DALT_BACKEND=1 -Ivirtualxt/lib/vxt -Ivirtualxt/lib/vxt/include
+ALT_Z80_CFLAGS=-DALT_BACKEND=1
 
 .PHONY: all
 all: \
@@ -17,6 +19,7 @@ emu_6809 \
 emu_6809_alt \
 basic_6809.ihx \
 emu_8086_alt \
+msbasic.ihx \
 emu_z80 \
 emu_z80_alt \
 zexall.ihx \
@@ -143,14 +146,19 @@ emu_8086.o: cpu_8086.h
 
 cpu_8086.o: cpu_8086.h
 
-emu_8086_alt: emu_8086_alt.o 8086.o
+emu_8086_alt: emu_8086_alt.o vxt_cpu.o
        ${CC} ${CFLAGS} -o $@ $^
 
-emu_8086_alt.o: emu_8086.c 8086/8086.h
-       ${CC} ${CFLAGS} -DALT_BACKEND=1 -o $@ -c $<
+emu_8086_alt.o: emu_8086.c virtualxt/lib/vxt/cpu.h
+       ${CC} ${CFLAGS} ${ALT_8086_CFLAGS} -o $@ -c $<
 
-8086.o: 8086/8086.c 8086/8086.h
-       ${CC} ${CFLAGS} -c $<
+vxt_cpu.o: virtualxt/lib/vxt/cpu.c virtualxt/lib/vxt/cpu.h
+       ${CC} ${CFLAGS} ${ALT_8086_CFLAGS} -o $@ -c $<
+
+msbasic.ihx: msbasic.com
+       ${BIN2HEX} --offset=0x600 $< __temp__.ihx
+       ./entry_point.py 0x50:0x100 __temp__.ihx $@
+       rm __temp__.ihx
 
 # Z80
 emu_z80: emu_z80.o cpu_z80.o
@@ -164,10 +172,10 @@ emu_z80_alt: emu_z80_alt.o z80.o
        ${CC} ${CFLAGS} -o $@ $^
 
 emu_z80_alt.o: emu_z80.c z80/z80.h
-       ${CC} ${CFLAGS} -DALT_BACKEND=1 -o $@ -c $<
+       ${CC} ${CFLAGS} ${ALT_Z80_CFLAGS} -o $@ -c $<
 
 z80.o: z80/z80.c z80/z80.h
-       ${CC} ${CFLAGS} -c $<
+       ${CC} ${CFLAGS} ${ALT_Z80_CFLAGS} -c $<
 
 zexall.ihx: ZEXALL/zexall.com
        ${BIN2HEX} --offset=0x100 $< __temp__.ihx
diff --git a/emu_8086.c b/emu_8086.c
new file mode 100644 (file)
index 0000000..3e4df71
--- /dev/null
@@ -0,0 +1,469 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if ALT_BACKEND
+#include "virtualxt/lib/vxt/cpu.h"
+#else
+#include "cpu_8086.h"
+#endif
+
+#define REG_TRACE 1
+#define MEM_TRACE 1
+#define IO_TRACE 1
+
+#define MEM_SIZE 0x100000
+uint8_t mem[MEM_SIZE];
+
+// read and write are separated for I/O
+#define IO_SIZE 0x100
+uint8_t io_read[IO_SIZE];
+uint8_t io_write[IO_SIZE];
+
+int load_ihx(char *name) {
+  FILE *fp = fopen(name, "r");
+  if (fp == NULL) {
+    perror(name);
+    exit(EXIT_FAILURE);
+  }
+
+  int base = 0, entry_point = 0;
+  bool had_eof = false;
+  char line[0x100];
+  while (fgets(line, 0x100, fp)) {
+    for (char *p = line; *p; ++p)
+      if (*p == '\n') {
+        *p = 0;
+        break;
+      }
+
+    if (had_eof) {
+      fprintf(stderr, "garbage after EOF record: %s\n", line);
+      exit(EXIT_FAILURE);
+    }
+
+    if (line[0] != ':') {
+      fprintf(stderr, "require colon: %s\n", line);
+      exit(EXIT_FAILURE);
+    }
+
+    uint8_t buf[0x7f];
+    int len;
+    for (len = 0; len < 0x7f; ++len) {
+      char *p = line + 1 + len * 2;
+      if (*p == 0)
+        break;
+      if (*p == '\n') {
+        *p = 0;
+        break;
+      }
+      uint8_t c = p[2];
+      p[2] = 0;
+
+      char *q;
+      buf[len] = (uint8_t)strtol(p, &q, 16);
+      p[2] = c;
+      if (q != p + 2) {
+        fprintf(stderr, "not hex byte: %s\n", p);
+        exit(EXIT_FAILURE);
+      }
+    }
+
+    if (len == 0) {
+      fprintf(stderr, "empty line: %s\n", line);
+      exit(EXIT_FAILURE);
+    }
+
+    uint8_t checksum = 0;
+    for (int i = 0; i < len; ++i)
+      checksum += buf[i];
+    if (checksum) {
+      checksum -= buf[len - 1];
+      fprintf(
+        stderr,
+        "checksum %02x, should be %02x\n",
+        checksum,
+        buf[len - 1]
+      );
+      exit(EXIT_FAILURE);
+    }
+
+    len -= 5;
+    if (len != buf[0]) {
+      fprintf(stderr, "incorrect length: %s\n", line);
+      exit(EXIT_FAILURE);
+    }
+
+    int addr = (buf[1] << 8) | buf[2], end_addr;
+    switch (buf[3]) {
+    case 0:
+      addr += base;
+      end_addr = addr + len;
+      if (end_addr < addr || end_addr > MEM_SIZE) {
+        fprintf(stderr, "invalid load range: [0x%x, 0x%x)\n", addr, end_addr);
+        exit(EXIT_FAILURE);
+      }
+      memcpy(mem + addr, buf + 4, len);
+      break;
+    case 1:
+      had_eof = true;
+      break;
+    case 3:
+      if (len < 4) {
+        fprintf(stderr, "invalid start address record: %s\n", line);
+        exit(EXIT_FAILURE);
+      }
+      entry_point = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+      break;
+    case 4:
+      if (len < 2) {
+        fprintf(stderr, "invalid extended linear address record: %s\n", line);
+        exit(EXIT_FAILURE);
+      }
+      base = (buf[4] << 24) | (buf[5] << 16);
+      break;
+#if 0
+    case 5:
+      if (len < 4) {
+        fprintf(stderr, "invalid start linear address record: %s\n", line);
+        exit(EXIT_FAILURE);
+      }
+      entry_point = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+      break;
+#endif
+    default:
+      fprintf(stderr, "unknown record type: 0x%x\n", buf[3]);
+      exit(EXIT_FAILURE);
+    }
+  }
+  if (!had_eof) {
+    fprintf(stderr, "no EOF record\n");
+    exit(EXIT_FAILURE);
+  }
+
+  fclose(fp);
+  return entry_point;
+}
+
+int msdos(int ax, int dx, int ds, bool *zf) {
+  switch (ax >> 8) {
+  case 2:
+    // Character output
+    dx &= 0xff;
+    switch (dx) {
+    case '\n':
+      break;
+    case '\r':
+      dx = '\n';
+    default:
+      putchar(dx);
+      break;
+    }
+    break;
+  case 6:
+    // Direct console I/O
+    dx &= 0xff;
+    if (dx == 0xff) {
+      int data = getchar();
+      switch (data) {
+      case '\n':
+        data = '\r';
+        goto avail;
+      case 0x7f:
+        data = '\b';
+        //goto avail;
+      default:
+      avail:
+        ax = data | 0x600;
+        *zf = true;
+        break;
+      case EOF:
+        *zf = false;
+        break;
+      }
+      break;
+    }
+    switch (dx) {
+    case '\n':
+      break;
+    case '\r':
+      dx = '\n';
+    default:
+      putchar(dx);
+      break;
+    }
+    break;
+  case 9:
+    // Display string
+    for (
+      int data;
+      (data = mem[dx + (ds << 4)]) != '$';
+      dx = (dx + 1) & 0xffff
+    )
+      switch (data) {
+      case '\n':
+        break;
+      case '\r':
+        data = '\n';
+      default:
+        putchar(data);
+        break;
+      }
+    break;
+  case 0x26:
+    // Create PSP -- ignore
+    break;
+  case 0x30:
+    // Get DOS version
+    ax = 0x211;
+    break;
+  case 0x33:
+    // Get or set Ctrl-Break -- ignore
+    break;
+  case 0x25:
+    // Set interrupt vector -- ignore
+    break;
+  case 0x35:
+    // Get interrupt vector -- ignore
+    break;
+  default:
+    fprintf(stderr, "unimplemented MSDOS call 0x%02x\n", ax >> 8);
+    exit(EXIT_FAILURE);
+  }
+  return ax;
+}
+
+int read_byte(void *context, int addr) {
+#if MEM_TRACE
+  int data = mem[addr];
+  fprintf(stderr, "addr=%05x rd=%02x\n", addr, data);
+  return data;
+#else
+  return mem[addr];
+#endif
+}
+
+void write_byte(void *context, int addr, int data) {
+#if MEM_TRACE
+  fprintf(stderr, "addr=%05x wr=%02x\n", addr, data);
+#endif
+  mem[addr] = data;
+}
+
+int in_byte(void *context, int addr) {
+  addr &= 0xff;
+#if IO_TRACE
+  int data = io_read[addr];
+  printf("io=%02x rd=%02x\n", addr, data);
+  return data;
+#else
+  return io_read[addr];
+#endif
+}
+
+void out_byte(void *context, int addr, int data) {
+  addr &= 0xff;
+#if IO_TRACE
+  printf("io=%02x wr=%02x\n", addr, data);
+#endif
+  io_write[addr] = data;
+}
+
+#if ALT_BACKEND
+#if defined(VXT_LIBC) && !defined(VXT_NO_LOGGING)
+#include <stdarg.h>
+static int libc_print(const char *fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  int ret = vprintf(fmt, args);
+  va_end(args);
+  return ret;
+}
+int (*logger)(const char*, ...) = &libc_print;
+#else
+static int no_print(const char *fmt, ...) {
+  UNUSED(fmt);
+  return -1;
+}
+int (*logger)(const char*, ...) = &no_print;
+#endif
+
+static void no_breakpoint(void) {}
+void (*breakpoint)(void) = &no_breakpoint;
+
+vxt_byte vxt_system_read_byte(CONSTP(vxt_system) s, vxt_pointer addr) {
+  addr &= 0xFFFFF;
+  return read_byte(NULL, addr);
+}
+
+void vxt_system_write_byte(CONSTP(vxt_system) s, vxt_pointer addr, vxt_byte data) {
+  addr &= 0xFFFFF;
+  write_byte(NULL, addr, data);
+}
+
+vxt_word vxt_system_read_word(CONSTP(vxt_system) s, vxt_pointer addr) {
+  int data = vxt_system_read_byte(s, addr);
+  return WORD(vxt_system_read_byte(s, addr + 1), data);
+}
+
+void vxt_system_write_word(CONSTP(vxt_system) s, vxt_pointer addr, vxt_word data) {
+  vxt_system_write_byte(s, addr, LBYTE(data));
+  vxt_system_write_byte(s, addr + 1, HBYTE(data));
+}
+
+vxt_byte system_in(CONSTP(vxt_system) s, vxt_word port) {
+  return in_byte(NULL, port);
+}
+
+void system_out(CONSTP(vxt_system) s, vxt_word port, vxt_byte data) {
+  out_byte(NULL, port, data);
+}
+#endif
+
+int main(int argc, char **argv) {
+  if (argc < 2) {
+    printf("usage: %s image.ihx\n", argv[0]);
+    exit(EXIT_FAILURE);
+  }
+  int entry_point = load_ihx(argv[1]);
+
+  // int 0x20 vector
+  mem[0x80] = 0;
+  mem[0x81] = 0;
+  mem[0x82] = 0x40;
+  mem[0x83] = 0;
+  mem[0x400] = 0xcf; // iret, for msdos emulation
+
+  // int 0x21 vector
+  mem[0x84] = 1;
+  mem[0x85] = 0;
+  mem[0x86] = 0x40;
+  mem[0x87] = 0;
+  mem[0x401] = 0xcf; // iret, for msdos emulation
+
+  // psp
+  int code_segment = (entry_point >> 16) & 0xffff;
+  mem[5 + (code_segment << 4)] = 0xc3; // ret, for bdos emulation
+  mem[6 + (code_segment << 4)] = 0xfe; // memory top, lo byte
+  mem[7 + (code_segment << 4)] = 0xff; // memory top, hi byte
+  mem[0x80 + (code_segment << 4)] = 0; // command line length
+  mem[0x81 + (code_segment << 4)] = '\r'; // command line sentinel
+
+#if ALT_BACKEND
+  struct cpu cpu;
+  memset(&cpu, 0, sizeof(struct cpu));
+  cpu_reset(&cpu);
+  //cpu.read_byte = rb;
+  //cpu.write_byte = wb;
+  //cpu.port_in = in;
+  //cpu.port_out = out;
+  cpu.regs.ip = entry_point & 0xffff;
+  cpu.regs.cs = code_segment;
+  cpu.regs.sp = 0xfffe;
+  cpu.regs.ss = code_segment;
+
+  while (true) {
+#if REG_TRACE
+    fprintf(
+      stderr,
+      "ip=%04x ax=%04x cx=%04x dx=%04x bx=%04x sp=%04x bp=%04x si=%04x di=%04x cs=%04x ds=%04x es=%04x ss=%04x flags=%04x cf=%d pf=%d af=%d zf=%d sf=%d tf=%d if=%d df=%d of=%d\n",
+      cpu.regs.ip,
+      cpu.regs.ax,
+      cpu.regs.cx,
+      cpu.regs.dx,
+      cpu.regs.bx,
+      cpu.regs.sp,
+      cpu.regs.bp,
+      cpu.regs.si,
+      cpu.regs.di,
+      cpu.regs.cs,
+      cpu.regs.ds,
+      cpu.regs.es,
+      cpu.regs.ss,
+      cpu.regs.flags,
+      cpu.regs.flags & 1,
+      (cpu.regs.flags >> 2) & 1,
+      (cpu.regs.flags >> 4) & 1,
+      (cpu.regs.flags >> 6) & 1,
+      (cpu.regs.flags >> 7) & 1,
+      (cpu.regs.flags >> 8) & 1,
+      (cpu.regs.flags >> 9) & 1,
+      (cpu.regs.flags >> 10) & 1,
+      (cpu.regs.flags >> 11) & 1
+    );
+#endif
+
+    if (
+      (cpu.regs.ip == 0 && cpu.regs.cs == code_segment) ||
+      (cpu.regs.ip == 0 && cpu.regs.cs == 0x40)
+    ) {
+      putchar('\n');
+      break;
+    }
+    if (cpu.regs.ip == 5 && cpu.regs.cs == code_segment) {
+      bool zf = (cpu.regs.flags >> 6) & 1;
+      cpu.regs.ax = msdos(cpu.regs.cl << 8, cpu.regs.dx, cpu.regs.ds, &zf);
+      cpu.regs.flags = (zf << 6) | (cpu.regs.flags & ~0x40);
+    }
+    else if (cpu.regs.ip == 1 && cpu.regs.cs == 0x40) {
+      bool zf = (cpu.regs.flags >> 6) & 1;
+      cpu.regs.ax = msdos(cpu.regs.ax, cpu.regs.dx, cpu.regs.ds, &zf);
+      cpu.regs.flags = (zf << 6) | (cpu.regs.flags & ~0x40);
+    }
+    cpu_step(&cpu);
+  }
+#else
+  struct cpu_8086 cpu;
+  cpu_8086_init(
+    &cpu,
+    read_byte,
+    NULL,
+    write_byte,
+    NULL,
+    in_byte,
+    NULL,
+    out_byte,
+    NULL
+  );
+  cpu_8086_reset(&cpu);
+  cpu.regs.word.pc = entry_point;
+
+  while (true) {
+#if REG_TRACE
+    fprintf(
+      stderr,
+      "pc=%04x af=%04x bc=%04x de=%04x hl=%04x sp=%04x ix=%04x iy=%04x cf=%d nf=%d pvf=%d hf=%d zf=%d sf=%d\n",
+      cpu.regs.word.pc,
+      cpu.regs.word.af,
+      cpu.regs.word.bc,
+      cpu.regs.word.de,
+      cpu.regs.word.hl,
+      cpu.regs.word.sp,
+      cpu.regs.word.ix,
+      cpu.regs.word.iy,
+      cpu.regs.bit.cf,
+      cpu.regs.bit.nf,
+      cpu.regs.bit.pvf,
+      cpu.regs.bit.hf,
+      cpu.regs.bit.zf,
+      cpu.regs.bit.sf
+    );
+#endif
+
+    if (cpu.regs.word.pc == 0) {
+      putchar('\n');
+      break;
+    }
+    if (cpu.regs.word.pc == 5) {
+      cpu.regs.word.de = bdos(
+        cpu.regs.byte.c,
+        cpu.regs.word.de
+      );
+    }
+    cpu_8086_execute(&cpu);
+  }
+#endif
+
+  return 0;
+}
index 39f878e..1636c6f 100755 (executable)
@@ -9,7 +9,7 @@ EXIT_FAILURE = 1
 if len(sys.argv) < 4:
   print(f'usage: {sys.argv[0]:s} entry_point in.ihx out.ihx')
   sys.exit(EXIT_FAILURE)
-entry_point = int(sys.argv[1], 0)
+entry_point = [int(i, 0) for i in sys.argv[1].split(':')]
 in_ihx = sys.argv[2]
 out_ihx = sys.argv[3]
 
@@ -17,5 +17,9 @@ intelhex = IntelHex(in_ihx)
 segments = [j for i in intelhex.segments() for j in i]
 for i in range(0, len(segments), 2):
   print(f'[{segments[i]:04x}, {segments[i + 1]:04x})')
-intelhex.start_addr = {'EIP': entry_point}
+intelhex.start_addr = (
+  {'EIP': entry_point[0]}
+if len(entry_point) == 1 else
+  {'CS': entry_point[0], 'IP': entry_point[1]}
+)
 intelhex.write_hex_file(out_ihx)
diff --git a/msbasic.com b/msbasic.com
new file mode 100644 (file)
index 0000000..b0e8b71
Binary files /dev/null and b/msbasic.com differ
diff --git a/msbasic_test.txt b/msbasic_test.txt
new file mode 100644 (file)
index 0000000..2230fd1
--- /dev/null
@@ -0,0 +1,58 @@
+10 PRINT "HELLO"
+20 REM P9=X9^Y9 === GOSUB 60030
+30 X9=.3:Y9=.7:GOSUB 60030:PRINT .3^.7,P9
+40 REM L9=LOG(X9) === GOSUB 60090
+50 X9=.4:GOSUB 60090:PRINT LOG(.4),L9
+60 REM E9=EXP(X9) === GOSUB 60160
+70 X9=.5:GOSUB 60160:PRINT EXP(.5),E9
+80 REM C9=COS(X9) === GOSUB 60240
+90 X9=.6:GOSUB 60240:PRINT COS(.6),C9
+100 REM T9=TAN(X9) === GOSUB 60280
+110 X9=.7:GOSUB 60280:PRINT TAN(.7),T9
+120 REM A9=ATN(X9) === GOSUB 60310
+130 X9=.8:GOSUB 60310:PRINT ATN(.8),A9
+140 END
+60000 REM EXPONENTIATION: P9=X9^Y9
+60010 REM NEED: EXP, LOG
+60020 REM VARIABLES USED: A9,B9,C9,E9,L9,P9,X9,Y9
+60030 P9=1 : E9=0 : IF Y9=0 THEN RETURN
+60040 IF X9<0 THEN IF INT(Y9)=Y9 THEN P9=1-2*Y9+4*INT(Y9/2) : X9=-X9
+60050 IF X9<>0 THEN GOSUB 60090 : X9=Y9*L9 : GOSUB 60160
+60060 P9=P9*E9 : RETURN
+60070 REM NATURAL LOGARITHM: L9=LOG(X9)
+60080 REM VARIABLES USED: A9,B9,C9,E9,L9,X9
+60090 E9=0 : IF X9<=0 THEN PRINT "LOG FC ERROR"; : STOP
+60095 A9=1 : B9=2 : C9=.5 : REM THIS WILL SPEED UP THE FOLLOWING
+60100 IF X9>=A9 THEN X9=C9*X9 : E9=E9+A9 : GOTO 60100
+60110 IF X9<C9 THEN X9=B9*X9 : E9=E9-A9 : GOTO 60110
+60120 X9=(X9-.707107)/(X9+.707107) : L9=X9*X9
+60130 L9=(((.598979*L9+.961471)*L9+2.88539)*X9+E9-.5)*.693147
+60135 RETURN
+60140 REM EXPONENTIAL: E9=EXP(X9)
+60150 REM VARIABLES USED: A9,E9,L9,X9
+60160 L9=INT(1.4427*X9)+1 : IF L9<1E7 THEN 60180
+60170 IF X9>0 THEN PRINT "EXP OV ERROR"; : STOP
+60175 E9=0 : RETURN
+60180 E9=.693147*L9-X9 : A9=1.32988E-3-1.41316E-4*E9
+60190 A9=((A9*E9-8.30136E-3)*E9+4.16574E-2)*E9
+60195 E9=(((A9-.166665)*E9+.5)*E9-1)*E9+1 : A9=2
+60197 IF L9<=0 THEN A9=.5 : L9=-L9 : IF L9=0 THEN RETURN
+60200 FOR X9=1 TO L9 : E9=A9*E9 : NEXT X9 : RETURN
+60210 REM COSINE: C9=COS(X9)
+60220 REM N.B. SIN MUST BE RETAINED AT LOAD-TIME
+60230 REM VARIABLES USED: C9,X9
+60240 C9=SIN(X9+1.5708) : RETURN
+60250 REM TANGENT: T9=TAN(X9)
+60260 REM NEEDS COS. (SIN NUST BE RETAINED AT LOAD-TIME)
+60270 REM VARIABLES USED: C9,T9,X9
+60280 GOSUB 60240 : T9=SIN(X9)/C9 : RETURN
+60290 REM ARCTANGENT: A9=ATN(X9)
+60300 REM VARIABLES USED: A9,B9,C9,T9,X9
+60310 T9=SGN(X9): X9=ABS(X9): C9=0 : IF X9>1 THEN C9=1 : X9=1/X9
+60320 A9=X9*X9 : B9=((2.86623E-3*A9-1.61657E-2)*A9+4.29096E-2)*A9
+60330 B9=((((B9-7.5289E-2)*A9+.106563)*A9-.142089)*A9+.199936)*A9
+60340 A9=((B9-.333332)*A9+1)*X9 : IF C9=1 THEN A9=1.5708-A9
+60350 A9=T9*A9 : RETURN
+RUN
+PRINT "DONE"
+SYSTEM
diff --git a/virtualxt b/virtualxt
new file mode 160000 (submodule)
index 0000000..a31bebe
--- /dev/null
+++ b/virtualxt
@@ -0,0 +1 @@
+Subproject commit a31bebeec7b3ee92db8f8b5bccb9effe1849dbb8