Add mbasic.ihx (CP/M BASIC-80), implement BIOS and BDOS calls in emu_z80(_alt)
authorNick Downing <nick@ndcode.org>
Fri, 28 Nov 2025 14:08:16 +0000 (01:08 +1100)
committerNick Downing <nick@ndcode.org>
Fri, 28 Nov 2025 14:08:16 +0000 (01:08 +1100)
.gitmodules
Makefile
cpm_compilers [new submodule]
emu_8086.c
emu_z80.c

index 48070eb..7ad9f5d 100644 (file)
@@ -43,3 +43,6 @@
 [submodule "pearpc"]
        path = pearpc
        url = https://github.com/nickd4/pearpc.git
+[submodule "cpm_compilers"]
+       path = cpm_compilers
+       url = https://github.com/davidly/cpm_compilers.git
index 673a5e3..4a0527b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,5 @@
+# run this first:
+# pip3 install --break-system-packages intelhex
 BIN2HEX=bin2hex.py
 
 CFLAGS=-g -Og -Wall -Wno-attributes -Wno-unused-function
@@ -30,7 +32,8 @@ basic_pdp11.ihx \
 emu_z80 \
 emu_z80_alt \
 zexall.ihx \
-zexdoc.ihx
+zexdoc.ihx \
+mbasic.ihx
 #emu_mips
 #emu_pdp11
 #emu_68000
@@ -172,6 +175,7 @@ basic_6809.ihx: ExBasROM.hex
 ExBasROM.hex: ExBasRom.zip
        rm -f $@
        unzip $< $@
+       touch $@
 
 ExBasRom.zip:
        rm -f $@
@@ -292,6 +296,11 @@ zexdoc.ihx: ZEXALL/zexdoc.com
        ./entry_point.py 0x100 __temp__.ihx $@
        rm __temp__.ihx
 
+mbasic.ihx: cpm_compilers/Microsoft\ BASIC-80\ v521/MBASIC.COM
+       ${BIN2HEX} --offset=0x100 "$<" __temp__.ihx
+       ./entry_point.py 0x100 __temp__.ihx $@
+       rm __temp__.ihx
+
 .PHONY: clean
 clean:
        rm -f \
diff --git a/cpm_compilers b/cpm_compilers
new file mode 160000 (submodule)
index 0000000..7496623
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 74966236c8472eb57522b726defa188f262b1928
index 3e4df71..b7e31ca 100644 (file)
@@ -9,9 +9,9 @@
 #include "cpu_8086.h"
 #endif
 
-#define REG_TRACE 1
-#define MEM_TRACE 1
-#define IO_TRACE 1
+#define REG_TRACE 0
+#define MEM_TRACE 0
+#define IO_TRACE 0
 
 #define MEM_SIZE 0x100000
 uint8_t mem[MEM_SIZE];
index 7b3b05e..95e7fd4 100644 (file)
--- a/emu_z80.c
+++ b/emu_z80.c
 #define MEM_TRACE 0
 #define IO_TRACE 0
 
+#define BDOS 0xffbb            // ret
+#define JMP_BOOT 0xffbc                // -3: Cold start routine
+#define JMP_WBOOT 0xffbf       //  0: Warm boot - reload command processor
+#define JMP_CONST 0xffc2       //  3: Console status
+#define JMP_CONIN 0xffc5       //  6: Console input
+#define JMP_CONOUT 0xffc8      //  9: Console output
+#define JMP_LIST 0xffcb                // 12: Printer output
+#define JMP_PUNCH 0xffce       // 15: Paper tape punch output
+#define JMP_READER 0xffd1      // 18: Paper tape reader input
+#define JMP_HOME 0xffd4                // 21: Move disc head to track 0
+#define JMP_SELDSK 0xffd7      // 24: Select disc drive
+#define JMP_SETTRK 0xffda      // 27: Set track number
+#define JMP_SETSEC 0xffdd      // 30: Set sector number
+#define JMP_SETDMA 0xffe0      // 33: Set DMA address
+#define JMP_READ 0xffe3                // 36: Read a sector
+#define JMP_WRITE 0xffe6       // 39: Write a sector
+#define JMP_LISTST 0xffe9      // 42: Status of list device
+#define JMP_SECTRAN 0xffec     // 45: Sector translation for skewing
+#define BIOS_BOOT 0xffef       // ret
+#define BIOS_WBOOT 0xfff0      // ret
+#define BIOS_CONST 0xfff1      // ret
+#define BIOS_CONIN 0xfff2      // ret
+#define BIOS_CONOUT 0xfff3     // ret
+#define BIOS_LIST 0xfff4       // ret
+#define BIOS_PUNCH 0xfff5      // ret
+#define BIOS_READER 0xfff6     // ret
+#define BIOS_HOME 0xfff7       // ret
+#define BIOS_SELDSK 0xfff8     // ret
+#define BIOS_SETTRK 0xfff9     // ret
+#define BIOS_SETSEC 0xfffa     // ret
+#define BIOS_SETDMA 0xfffb     // ret
+#define BIOS_READ 0xfffc       // ret
+#define BIOS_WRITE 0xfffd      // ret
+#define BIOS_LISTST 0xfffe     // ret
+#define BIOS_SECTRAN 0xffff    // ret
+
 #define MEM_SIZE 0x10000
 uint8_t mem[MEM_SIZE];
 
@@ -137,25 +173,123 @@ int load_ihx(char *name) {
   return entry_point;
 }
 
-int bdos(int c, int de) {
+int bdos(int a, int c, int de, uint16_t *hl) {
   switch (c) {
   case 2:
+    // Character output
     de &= 0xff;
-    if (de != '\r')
+    switch (de) {
+    case '\n':
+      break;
+    case '\r':
+      de = '\n';
+    default:
       putchar(de);
+      break;
+    }
     break;
-  case 9:
-    while (mem[de] != '$') {
-      if (mem[de] != '\r')
-        putchar(mem[de]);
-      de = (de + 1) & 0xffff;
+  case 6:
+    // Direct console I/O
+    de &= 0xff;
+    if (de == 0xff) {
+      int data = getchar();
+      switch (data) {
+      case '\n':
+        data = '\r';
+        goto avail;
+      case 0x7f:
+        data = '\b';
+        //goto avail;
+      default:
+      avail:
+        a = data;
+        break;
+      case EOF:
+        a = 0;
+        break;
+      }
+      break;
+    }
+    switch (de) {
+    case '\n':
+      break;
+    case '\r':
+      de = '\n';
+    default:
+      putchar(de);
+      break;
     }
     break;
+  case 9:
+    // Display string
+    for (int data; (data = mem[de]) != '$'; de = (de + 1) & 0xffff)
+      switch (data) {
+      case '\n':
+        break;
+      case '\r':
+        data = '\n';
+      default:
+        putchar(data);
+        break;
+      }
+    break;
+  case 0xc:
+    // Get CP/M version
+    *hl = 0x22;
+    break;
+  case 0x29: // DRV_ROVEC - Return bitmap of read-only drives
+    *hl = 0;
+    break;
   default:
     fprintf(stderr, "unimplemented BDOS call 0x%02x\n", c);
     exit(EXIT_FAILURE);
   }
-  return de;
+  return a;
+}
+
+int bios(int pc, int a, int c) {
+  switch (pc) {
+  case BIOS_WBOOT:
+    exit(EXIT_SUCCESS);
+  case BIOS_CONST:
+    a = 0; // defeat BASIC Ctrl-C checking
+    break;
+  case BIOS_CONIN:
+    {
+      int data = getchar();
+      switch (data) {
+      case '\n':
+        data = '\r';
+        goto avail;
+      case 0x7f:
+        data = '\b';
+        //goto avail;
+      default:
+      avail:
+        a = data;
+        break;
+      case EOF:
+        a = 0;
+        break;
+      }
+    }
+    break;
+  case BIOS_CONOUT:
+    switch (c) {
+    case '\n':
+      break;
+    case '\r':
+      c = '\n';
+    default:
+      putchar(c);
+      break;
+    }
+    break;
+  default:
+    fprintf(stderr, "unimplemented BIOS call 0x%04x\n", pc);
+    exit(EXIT_FAILURE);
+  }
+  return a;
 }
 
 int read_byte(void *context, int addr) {
@@ -219,9 +353,101 @@ int main(int argc, char **argv) {
   }
   int entry_point = load_ihx(argv[1]);
 
-  mem[5] = 0xc9; // ret, for bdos emulation
-  mem[6] = 0xff; // memory top, lo byte
-  mem[7] = 0xff; // memory top, hi byte
+  mem[0] = 0xc3; // jmp
+  mem[1] = (uint8_t)JMP_WBOOT;
+  mem[2] = (uint8_t)(JMP_WBOOT >> 8);
+
+  mem[5] = 0xc3; // jmp
+  mem[6] = (uint8_t)BDOS;
+  mem[7] = (uint8_t)(BDOS >> 8);
+
+  mem[BDOS] = 0xc9; // ret
+
+  mem[JMP_BOOT] = 0xc3; // jmp
+  mem[JMP_BOOT + 1] = (uint8_t)BIOS_BOOT;
+  mem[JMP_BOOT + 2] = (uint8_t)(BIOS_BOOT >> 8);
+
+  mem[JMP_WBOOT] = 0xc3; // jmp
+  mem[JMP_WBOOT + 1] = (uint8_t)BIOS_WBOOT;
+  mem[JMP_WBOOT + 2] = (uint8_t)(BIOS_WBOOT >> 8);
+
+  mem[JMP_CONST] = 0xc3; // jmp
+  mem[JMP_CONST + 1] = (uint8_t)BIOS_CONST;
+  mem[JMP_CONST + 2] = (uint8_t)(BIOS_CONST >> 8);
+
+  mem[JMP_CONIN] = 0xc3; // jmp
+  mem[JMP_CONIN + 1] = (uint8_t)BIOS_CONIN;
+  mem[JMP_CONIN + 2] = (uint8_t)(BIOS_CONIN >> 8);
+
+  mem[JMP_CONOUT] = 0xc3; // jmp
+  mem[JMP_CONOUT + 1] = (uint8_t)BIOS_CONOUT;
+  mem[JMP_CONOUT + 2] = (uint8_t)(BIOS_CONOUT >> 8);
+
+  mem[JMP_LIST] = 0xc3; // jmp
+  mem[JMP_LIST + 1] = (uint8_t)BIOS_LIST;
+  mem[JMP_LIST + 2] = (uint8_t)(BIOS_LIST >> 8);
+
+  mem[JMP_PUNCH] = 0xc3; // jmp
+  mem[JMP_PUNCH + 1] = (uint8_t)BIOS_PUNCH;
+  mem[JMP_PUNCH + 2] = (uint8_t)(BIOS_PUNCH >> 8);
+
+  mem[JMP_READER] = 0xc3; // jmp
+  mem[JMP_READER + 1] = (uint8_t)BIOS_READER;
+  mem[JMP_READER + 2] = (uint8_t)(BIOS_READER >> 8);
+
+  mem[JMP_HOME] = 0xc3; // jmp
+  mem[JMP_HOME + 1] = (uint8_t)BIOS_HOME;
+  mem[JMP_HOME + 2] = (uint8_t)(BIOS_HOME >> 8);
+
+  mem[JMP_SELDSK] = 0xc3; // jmp
+  mem[JMP_SELDSK + 1] = (uint8_t)BIOS_SELDSK;
+  mem[JMP_SELDSK + 2] = (uint8_t)(BIOS_SELDSK >> 8);
+
+  mem[JMP_SETTRK] = 0xc3; // jmp
+  mem[JMP_SETTRK + 1] = (uint8_t)BIOS_SETTRK;
+  mem[JMP_SETTRK + 2] = (uint8_t)(BIOS_SETTRK >> 8);
+
+  mem[JMP_SETSEC] = 0xc3; // jmp
+  mem[JMP_SETSEC + 1] = (uint8_t)BIOS_SETSEC;
+  mem[JMP_SETSEC + 2] = (uint8_t)(BIOS_SETSEC >> 8);
+
+  mem[JMP_SETDMA] = 0xc3; // jmp
+  mem[JMP_SETDMA + 1] = (uint8_t)BIOS_SETDMA;
+  mem[JMP_SETDMA + 2] = (uint8_t)(BIOS_SETDMA >> 8);
+
+  mem[JMP_READ] = 0xc3; // jmp
+  mem[JMP_READ + 1] = (uint8_t)BIOS_READ;
+  mem[JMP_READ + 2] = (uint8_t)(BIOS_READ >> 8);
+
+  mem[JMP_WRITE] = 0xc3; // jmp
+  mem[JMP_WRITE + 1] = (uint8_t)BIOS_WRITE;
+  mem[JMP_WRITE + 2] = (uint8_t)(BIOS_WRITE >> 8);
+
+  mem[JMP_LISTST] = 0xc3; // jmp
+  mem[JMP_LISTST + 1] = (uint8_t)BIOS_LISTST;
+  mem[JMP_LISTST + 2] = (uint8_t)(BIOS_LISTST >> 8);
+
+  mem[JMP_SECTRAN] = 0xc3; // jmp
+  mem[JMP_SECTRAN + 1] = (uint8_t)BIOS_SECTRAN;
+  mem[JMP_SECTRAN + 2] = (uint8_t)(BIOS_SECTRAN >> 8);
+
+  mem[BIOS_BOOT] = 0xc9; // ret
+  mem[BIOS_WBOOT] = 0xc9; // ret
+  mem[BIOS_CONST] = 0xc9; // ret
+  mem[BIOS_CONIN] = 0xc9; // ret
+  mem[BIOS_CONOUT] = 0xc9; // ret
+  mem[BIOS_LIST] = 0xc9; // ret
+  mem[BIOS_PUNCH] = 0xc9; // ret
+  mem[BIOS_READER] = 0xc9; // ret
+  mem[BIOS_HOME] = 0xc9; // ret
+  mem[BIOS_SELDSK] = 0xc9; // ret
+  mem[BIOS_SETTRK] = 0xc9; // ret
+  mem[BIOS_SETSEC] = 0xc9; // ret
+  mem[BIOS_SETDMA] = 0xc9; // ret
+  mem[BIOS_READ] = 0xc9; // ret
+  mem[BIOS_WRITE] = 0xc9; // ret
+  mem[BIOS_LISTST] = 0xc9; // ret
+  mem[BIOS_SECTRAN] = 0xc9; // ret
 
 #if ALT_BACKEND
   z80 cpu;
@@ -231,6 +457,7 @@ int main(int argc, char **argv) {
   cpu.port_in = in;
   cpu.port_out = out;
   cpu.pc = entry_point;
+  cpu.sp = BDOS - 2; // assume word at BDOS - 2 is initialized to 0
 
   while (true) {
 #if REG_TRACE
@@ -262,16 +489,16 @@ int main(int argc, char **argv) {
     );
 #endif
 
-    if (cpu.pc == 0) {
-      putchar('\n');
-      break;
-    }
-    if (cpu.pc == 5) {
+    if (cpu.pc == BDOS) {
       int de = cpu.e | (cpu.d << 8);
-      de = bdos(cpu.c, de);
-      cpu.e = de & 0xff;
-      cpu.d = de >> 8;
+      uint16_t hl = cpu.l | (cpu.h << 8);
+      cpu.a = bdos(cpu.a, cpu.c, de, &hl);
+      cpu.l = (uint8_t)hl;
+      cpu.h = (uint8_t)(hl >> 8);
     }
+    else if (cpu.pc >= BIOS_BOOT)
+      cpu.a = bios(cpu.pc, cpu.a, cpu.c);
+
     z80_step(&cpu, 1);
   }
 #else
@@ -289,6 +516,7 @@ int main(int argc, char **argv) {
   );
   cpu_z80_reset(&cpu);
   cpu.regs.word.pc = entry_point;
+  cpu.regs.word.sp = BDOS - 2; // assume word at BDOS - 2 is initialized to 0
 
   while (true) {
 #if REG_TRACE
@@ -312,16 +540,20 @@ int main(int argc, char **argv) {
     );
 #endif
 
-    if (cpu.regs.word.pc == 0) {
-      putchar('\n');
-      break;
-    }
-    if (cpu.regs.word.pc == 5) {
-      cpu.regs.word.de = bdos(
+    if (cpu.regs.word.pc == BDOS)
+      cpu.regs.byte.a = bdos(
+        cpu.regs.byte.a,
         cpu.regs.byte.c,
-        cpu.regs.word.de
+        cpu.regs.word.de,
+        &cpu.regs.word.hl
       );
-    }
+    else if (cpu.regs.word.pc >= BIOS_BOOT)
+      cpu.regs.byte.a = bios(
+        cpu.regs.word.pc,
+        cpu.regs.byte.a,
+        cpu.regs.byte.c
+      );
+
     cpu_z80_execute(&cpu);
   }
 #endif