From c1fd3d91eb1c88a578b51a3463db0c7073ea3219 Mon Sep 17 00:00:00 2001 From: Nick Downing Date: Sat, 29 Nov 2025 01:08:16 +1100 Subject: [PATCH] Add mbasic.ihx (CP/M BASIC-80), implement BIOS and BDOS calls in emu_z80(_alt) --- .gitmodules | 3 + Makefile | 11 +- cpm_compilers | 1 + emu_8086.c | 6 +- emu_z80.c | 286 +++++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 276 insertions(+), 31 deletions(-) create mode 160000 cpm_compilers diff --git a/.gitmodules b/.gitmodules index 48070eb..7ad9f5d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/Makefile b/Makefile index 673a5e3..4a0527b 100644 --- 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 index 0000000..7496623 --- /dev/null +++ b/cpm_compilers @@ -0,0 +1 @@ +Subproject commit 74966236c8472eb57522b726defa188f262b1928 diff --git a/emu_8086.c b/emu_8086.c index 3e4df71..b7e31ca 100644 --- a/emu_8086.c +++ b/emu_8086.c @@ -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]; diff --git a/emu_z80.c b/emu_z80.c index 7b3b05e..95e7fd4 100644 --- a/emu_z80.c +++ b/emu_z80.c @@ -13,6 +13,42 @@ #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 -- 2.34.1