I've really enjoyed reading some of Matthew Garrett's entries about legacy PC hardware features - specifically the cute hack involving A20 and the keyboard controller. Reading things like that really takes me back.
I remember when the z80 was cutting edge, and I discovered you could switch to a whole new set of shadow registers via "exx" and "ex af,af'". I remember using undocumented opcodes, and even now I can assemble and disassemble simple z80 machine code. (Don't get me started on the speedlock protectors and their fiendish use of the R register; that'll stick in your mind if you cracked it. I did.)
I remember being introduced to the PC, seeing subdirectories appear in MS-DOS 2.0, network redirectors appearing in DOS 3.x, and support for big hard drives appearing in MS-DOS 4.0.
I remember the controvosy over the AARD code in the betas of Windows 3, and the focus that was given in the book "Undocumented DOS" which mostly focussed upon the "list of lists". (At that time I'd have been running GEM on an IBM XT with hercules (monochrome) graphics.)
I remember learning about that a .COM file was a flat image, limited to 64k which loaded at offset 100h, to accomodate the PSP (program segement prefix) for compatbility with CP/M (something I've never seen, never used, and known nothing about. I just know you could use the file control blocks to get simple wildcard handling for your programs "for free")
I wrote simple viral code which exploited this layout, appending new code to end of binary, replacing the first three bytes with a jump to the freshly added code. Later you could restore the original bytes back to their original contents and jump back to 100h (I even got the pun in the name of 40Hex magazine.)
I recall when COM files started to go out of fashion and you had to deal with relocation and segment fixups. When MZ was an important figure.
I even remember further back to when switching to protected mode was a hell of tripple-faults, switching back from protected mode to real mode was "impossible" and the delight to be had with the discovery and exploitation of "unreal mode".
All these things I remember .. and yet .. they're meaningless to most now, merely old history. Adults these days might have grown up and reached age 20 having always had Windows 95 - never having seen, used, or enjoyed MS-DOS.
How far have we come since then? In some ways not far at all. In other ways the rise of technology has been exponential.
Back then when I was doing things I'd not have dreamed I could update a webpage from a mobile phone whilst trapped upon a stalled train.
There are now more mobile phones than people in the UK. In some ways that's frightening - People miss the clear seperation between home & work for example - but in other ways .. liberation.
I have no predictions for the future, but it amazing how far we've come just in my lifetime; and largely without people noticing.
The industrial revolution? Did that happen with people mostly not noticing? Or was there more concious awareness? Food for thought.
I've been tinkering with hardware for a couple of years now, most of this
is trivial stuff if I'm honest, for example:
Wiring a display to a WiFi-enabled ESP8266 device.
Making it fetch data over the internet and display it.
Hooking up a temperature/humidity sensor to a device.
Submit readings to an MQ bus.
Off-hand I think the most complex projects I've built have been complex
in terms of software. For example I recently hooked up a 933Mhz radio-receiver
to an ESP8266 device, then had to reverse engineer the protocol of the device
I wanted to listen for. I recorded a radio-burst using an SDR dongle on
my laptop, broke the transmission into 1 and 0 manually, worked out the payload
and then ported that code to the ESP8266 device.
Anyway I've decided I should do something more complex, I should build
"a computer". Going old-school I'm going to stick to what I know best
the Z80 microprocessor. I started programming as a child with a ZX Spectrum which is built around
a Z80.
Initially I started with BASIC, later I moved on to assembly language mostly
because I wanted to hack games for infinite lives. I suspect the reason I don't
play video-games so often these days is because I'm just not very good without
cheating ;)
Anyway the Z80 is a reasonably simple processor, available in a 40PIN DIP format. There are the obvious connectors for power, ground, and a clock-source to make the thing tick. After that there are pins for the address-bus, and pins for the data-bus. Wiring up a standalone Z80 seems to be pretty trivial.
Of course making the processor "go" doesn't really give you much. You can wire it up, turn on the power, and barring explosions what do you have? A processor executing NOP instructions with no way to prove it is alive.
So to make a computer I need to interface with it. There are two obvious things that are required:
The ability to get your code on the thing.
i.e. It needs to read from memory.
The ability to read/write externally.
i.e. Light an LED, or scan for keyboard input.
I'm going to keep things basic at the moment, no pun intended. Because I have no RAM, because I have no ROM, because I have no keyboard I'm going to .. fake it.
The Z80 has 40 pins, of which I reckon we need to cable up over half. Only the arduino mega has enough pins for that, but I think if I use a Mega I can wire it to the Z80 then use the Arduino to drive it:
That means the Arduino will generate a clock-signal to make the Z80 tick.
The arduino will monitor the address-bus
When the Z80 makes a request to read the RAM at address 0x0000 it will return something from its memory.
When the Z80 makes a request to write to the RAM at address 0xffff it will store it away in its memory.
Similarly I can monitor for requests for I/O and fake that.
In short the Arduino will run a sketch with a 1024 byte array, which the Z80 will believe is its memory. Via the serial console I can read/write to that RAM, or have the contents hardcoded.
I thought I was being creative with this approach, but it seems like it has been done before, numerous times. For example:
Anyway I've ordered a bunch of Z80 chips, and an Arduino mega (since I own only one Arduino, I moved on to ESP8266 devices pretty quickly), so once it arrives I'll document the process further.
Once it works I'll need to slowly remove the arduino stuff - I guess I'll start by trying to build an external RAM/ROM interface, or an external I/O circuit. But basically:
Hook the Z80 up to the Arduino such that I can run my own code.
Then replace the arduino over time with standalone stuff.
The end result? I guess I have no illusions I can connect a full-sized keyboard to the chip, and drive a TV. But I bet I could wire up four buttons and an LCD panel. That should be enough to program a game of Tetris in Z80 assembly, and declare success. Something like that anyway :)
Expect part two to appear after my order of parts arrives from China.
My previous post on the subject of building a Z80-based computer briefly
explained my motivation, and the approach I was going to take.
This post describes my progress so far:
On the hardware side, zero progress.
On the software-side, lots of fun.
To recap I expect to wire a Z80 microprocessor to an Arduino (mega). The
arduino will generate a clock-signal which will make the processor "tick".
It will also react to read/write attempts that the processor makes to
access RAM, and I/O devices.
The Z80 has a neat system for requesting I/O, via the use of the IN and OUT
instructions which allow the processor to read/write a single byte to one of
255 connected devices.
With the appropriate tools available I could write some simple code. I implemented two I/O routines in the emulator, one to read a character from STDIN, and one to write to STDOUT:
IN A, (1) ; Read a character from STDIN, store in A-register.
OUT (1), A ; Write the character in A-register to STDOUT
With those primitives implemented I wrote a simple script:
;
; Simple program to upper-case a string
;
org 0
; show a prompt.
ld a, '>'
out (1), a
start:
; read a character
in a,(1)
; eof?
cp -1
jp z, quit
; is it lower-case? If not just output it
cp 'a'
jp c,output
cp 'z'
jp nc, output
; convert from lower-case to upper-case. yeah. math.
sub a, 32
output:
; output the character
out (1), a
; repeat forever.
jr start
quit:
; terminate
halt
And that's where I'll leave it for now. When I have the real hardware I'll
hookup some fake-RAM containing this program, and code a similar I/O handler
to allow reading/writing to the arduino's serial-console. That will allow
the same code to run, unchanged. That'd be nice.
I've got a simple Z80-manager written, but since I don't have the chips yet
I can only compile-test it. We'll see how well I did soon enough.
This is part three in my slow journey towards creating a home-brew
Z80-based computer. My previous
post
demonstrated writing some simple code, and getting it running under an
emulator. It also described my planned approach:
Hookup a Z80 processor to an Arduino Mega.
Run code on the Arduino to emulate RAM reads/writes and I/O.
Profit, via the learning process.
I expect I'll have to get my hands-dirty with a breadboard and naked
chips in the near future, but for the moment I decided to start with the
least effort. Erturk Kocalar has a website where he
sells "shields"
(read: expansion-boards) which contain a Z80, and which is designed to
plug into an Arduino Mega with no fuss. This is a simple design, I've
seen a bunch of people demonstrate how to wire up by hand, for example
this post.
Anyway I figured I'd order one of those, and get started on the easy-part, the software. There was some sample code available from Erturk, but it wasn't ideal from my point of view because it mixed driving the Z80 with doing "other stuff". So I abstracted the core code required to interface with the Z80 and packaged it as a simple library.
The end result is that I have a z80 retroshield library which uses an Arduino mega to drive a Z80 with something as simple as this:
#include <z80retroshield.h>
//
// Our program, as hex.
//
unsigned char rom[32] =
{
0x3e, 0x48, 0xd3, 0x01, 0x3e, 0x65, 0xd3, 0x01, 0x3e, 0x6c, 0xd3, 0x01,
0xd3, 0x01, 0x3e, 0x6f, 0xd3, 0x01, 0x3e, 0x0a, 0xd3, 0x01, 0xc3, 0x16,
0x00
};
//
// Our helper-object
//
Z80RetroShield cpu;
//
// RAM I/O function handler.
//
char ram_read(int address)
{
return (rom[address]) ;
}
// I/O function handler.
void io_write(int address, char byte)
{
if (address == 1)
Serial.write(byte);
}
// Setup routine: Called once.
void setup()
{
Serial.begin(115200);
//
// Setup callbacks.
//
// We have to setup a RAM-read callback, otherwise the program
// won't be fetched from RAM and executed.
//
cpu.set_ram_read(ram_read);
//
// Then we setup a callback to be executed every time an "out (x),y"
// instruction is encountered.
//
cpu.set_io_write(io_write);
//
// Configured.
//
Serial.println("Z80 configured; launching program.");
}
//
// Loop function: Called forever.
//
void loop()
{
// Step the CPU.
cpu.Tick();
}
All the logic of the program is contained in the Arduino-sketch, and all
the use of pins/ram/IO is hidden away. As a recap the Z80 will make
requests for memory-contents, to fetch the instructions it wants to
execute. For general purpose input/output there are two instructions
that are used:
IN A, (1) ; Read a character from STDIN, store in A-register.
OUT (1), A ; Write the character in A-register to STDOUT
Here 1 is the I/O address, and this is an 8 bit number. At the moment
I've just configured the callback such that any write to I/O address 1
is dumped to the serial console.
Anyway I put together a couple of examples of increasing complexity, allowing me to prove that RAM read/writes work, and that I/O reads and writes work.
I guess the next part is where I jump in complexity:
I need to wire a physical Z80 to a board.
I need to wire a PROM to it.
This will contain the program to be executed - hardcoded.
I need to provide power, and a clock to make the processor tick.
With a bunch of LEDs I'll have a Z80-system running, but it'll be
isolated and hard to program. (Since I'll need to reflash the
RAM/ROM-chip).
The next step would be getting it hooked up to a serial-console of some
sort. And at that point I'll have a genuinely programmable standalone
Z80 system.
In the past, I've talked about building a Z80-based computer. I made some progress towards that goal, in the sense that I took the initial (trivial steps) towards making something:
I built a clock-circuit.
I wired up a Z80 processor to the clock.
I got the thing running an endless stream of NOP instructions.
No RAM/ROM connected, tying all the bus-lines low, meaning every attempted memory-read returned 0x00 which is the Z80 NOP instruction.
But then I stalled, repeatedly, at designing an interface to RAM and ROM, so that it could actually do something useful. Over the lockdown I've been in two minds about getting sucked back down the rabbit-hole, so I compromised. I did a bit of searching on tindie, and similar places, and figured I'd buy a Z80-based single board computer. My requirements were minimal:
It must run CP/M.
The source-code to "everything" must be available.
I want it to run standalone, and connect to a host via a serial-port.
With those goals there were a bunch of boards to choose from, rc2014 is the standard choice - a well engineered system which uses a common backplane and lets you build mini-boards to add functionality. So first you build the CPU-card, then the RAM card, then the flash-disk card, etc. Over-engineered in one sense, extensible in another. (There are some single-board variants to cut down on soldering overhead, at a cost of less flexibility.)
The advantage of this design is that it loads code from a USB stick, making it easy to transfer files to/from it, without the need for a compact flash card, or similar. The downside is that the system has only 64K RAM, meaning it cannot run CP/M 3, only 2.2. (CP/M 3.x requires more RAM, and a banking/paging system setup to swap between pages.)
When the system boots it loads code from an EEPROM, which then fetches the CP/M files from the USB-stick, copies them into RAM and executes them. The memory map can be split so you either have ROM & RAM, or you have just RAM (after the boot the ROM will be switched off). To change the initial stuff you need to reprogram the EEPROM, after that it's just a matter of adding binaries to the stick or transferring them over the serial port.
In only a couple of hours I got the basic stuff working as well as I needed:
A z80-assembler on my Linux desktop to build simple binaries.
An installation of Turbo Pascal 3.00A on the system itself.
The Zork trilogy installed, along with Hitchhikers guide.
I had some fun with a CP/M emulator to get my hand back in things before the board arrived, and using that I tested my first "real" assembly language program (cls to clear the screen), as well as got the hang of using the wordstar keyboard shortcuts as used within the turbo pascal environment.
I have some plans for development:
Add command-line history (page-up/page-down) for the CP/M command-processor.
Add paging to TYPE, and allow terminating with Q.
Nothing major, but fun changes that won't be too difficult to implement.
Since CP/M 2.x has no concept of sub-directories you end up using drives for everything, I implemented a "search-path" so that when you type "FOO" it will attempt to run "A:FOO.COM" if there is no file matching on the current-drive. That's a nicer user-experience at all.
I also wrote some Z80-assembly code to search all drives for an executable, if not found in current drive and not already qualified. Remember CP/M doesn't have a concept of sub-directories) that's actually pretty useful:
B>LOCATE H*.COM
P:HELLO COM
P:HELLO2 COM
G:HITCH COM
E:HYPHEN COM
I've also written some other trivial assembly language tools, which was surprisingly relaxing. Especially once I got back into the zen mode of optimizing for size.
I forked the upstream repository, mostly to tidy up the contents, rather than because I want to go into my own direction. I'll keep the contents in sync, because there's no point splitting a community even further - I guess there are fewer than 100 of these boards in the wild, probably far far fewer!
In my previous post I wrote about how I'd been running CP/M on a Z80-based single-board computer.
I've been slowly working my way through a bunch of text-based adventure games:
The Hitchhiker's Guide To The Galaxy
Zork 1
Zork 2
Zork 3
Along the way I remembered how much fun I used to have doing this in my early teens, and decided to write my own text-based adventure.
Since I'm not a masochist I figured I'd write something with only three or four locations, and solicited facebook for ideas. Shortly afterwards a "plot" was created and I started work.
I figured that the very last thing I wanted to be doing was to be parsing text-input with Z80 assembly language, so I hacked up a simple adventure game in C. I figured if I could get the design right that would ease the eventual port to assembly.
I had the realization pretty early that using a table-driven approach would be the best way - using structures to contain the name, description, and function-pointers appropriate to each object for example. In my C implementation I have things that look like this:
{name: "generator",
desc: "A small generator.",
use: use_generator,
use_carried: use_generator_carried,
get_fn: get_generator,
drop_fn: drop_generator},
A bit noisy, but simple enough. If an object cannot be picked up, or dropped, the corresponding entries are blank:
{name: "desk",
desc: "",
edesc: "The desk looks solid, but old."},
Here we see something that is special, there's no description so the item isn't displayed when you enter a room, or LOOK. Instead the edesc (extended description) is available when you type EXAMINE DESK.
Anyway over a couple of days I hacked up the C-game, then I started work porting it to Z80 assembly. The implementation changed, the easter-eggs were different, but on the whole the two things are the same.
Certainly 99% of the text was recycled across the two implementations.
Anyway in the unlikely event you've got a craving for a text-based adventure game I present to you:
Back in April 2021 I introduced a simple text-based adventure game, The Lighthouse of Doom, which I'd written in Z80 assembly language for CP/M systems.
To recap my game is a simple text-based adventure game, which you can complete in fifteen minutes, or less, with a bunch of Paw Patrol easter-eggs.
You enter simple commands such as "up", "down", "take rug", etc etc.
You receive text-based replies "You can't see a telephone to use here!".
My code is largely table-based, having structures that cover objects, locations, and similar state-things. Most of the code involves working with those objects, with only a few small platform-specific routines being necessary:
Clearing the screen.
Pausing for "a short while".
Reading a line of input from the user.
Sending a $-terminated string to the console.
etc.
My feeling was that I could replace the use of those CP/M functions with something custom, and I'd have done the 99% of the work. Of course the devil is always in the details.
Let's start. To begin with I'm lucky in that I'm using the pasmo assembler which is capable of outputting .TAP files, which can be loaded into ZX Spectrum emulators.
I'm not going to walk through all the code here, because that is available within the project repository, but here's a very brief getting-started guide which demonstrates writing some code on a Linux host, and generating a TAP file which can be loaded into your favourite emulator. As I needed similar routines I started working out how to read keyboard input, clear the screen, and output messages which is what the following sample will demonstrate..
First of all you'll need to install the dependencies, specifically the assembler and an emulator to run the thing:
# apt install pasmo spectemu-x11
Now we'll create a simple assembly-language file, to test things out - save the following as hello.z80:
; Code starts here
org 32768
; clear the screen
call cls
; output some text
ld de, instructions ; DE points to the text string
ld bc, instructions_end-instructions ; BC contains the length
call 8252
; wait for a key
ld hl,0x5c08 ; LASTK
ld a,255
ld (hl),a
wkey:
cp (hl) ; wait for the value to change
jr z, wkey
; get the key and save it
ld a,(HL)
push af
; clear the screen
call cls
; show a second message
ld de, you_pressed
ld bc, you_pressed_end-you_pressed
call 8252
;; Output the ASCII character in A
ld a,2
call 0x1601
pop af
call 0x0010
; loop forever. simple demo is simple
endless:
jr endless
cls:
ld a,2
call 0x1601 ; ROM_OPEN_CHANNEL
call 0x0DAF ; ROM_CLS
ret
instructions:
defb 'Please press a key to continue!'
instructions_end:
you_pressed:
defb 'You pressed:'
you_pressed_end:
end 32768
Now you can assemble that into a TAP file like so:
$ pasmo --tapbas hello.z80 hello.tap
The final step is to load it in the emulator:
$ xspect -quick-load -load-immed -tap hello.tap
The reason I specifically chose that emulator was because it allows easily loading of a TAP file, without waiting for the tape to play, and without the use of any menus. (If you can tell me how to make FUSE auto-start like that, I'd love to hear!)
I wrote a small number of "CP/M emulation functions" allowing me to clear the screen, pause, prompt for input, and output text, which will work via the primitives available within the standard ZX Spectrum ROM. Then I reworked the game a little to cope with the different screen resolution (though only minimally, some of the text still breaks lines in unfortunate spots):
The end result is reasonably playable, even if it isn't quite as nice as the CP/M version (largely because of the unfortunate word-wrapping, and smaller console-area). So now my repository contains a .TAP file which can be loaded into your emulator of choice, available from the releases list.
Here's a brief teaser of what you can expect:
Outstanding bugs? Well the line-input is a bit horrid, and unfortunately this was written for CP/M accessed over a terminal - so I'd assumed a "standard" 80x25 resolution, which means that line/word-wrapping is broken in places.
That said it didn't take me too long to make the port, and it was kinda fun.
Recently I've been getting much more interested in the "retro" computers of my youth, partly because I've been writing crazy code in Z80 assembly-language, and partly because I've been preparing to introduce our child to his first computer:
An actual 1982 ZX Spectrum, cassette deck and all.
No internet
No hi-rez graphics
Easily available BASIC
And as a nice bonus the keyboard is wipe-clean!
I've got a few books, books I've hoarded for 30+ years, but I'd love to collect some more. So here's my request:
If you have any books covering either the Z80 processor, or the ZX Spectrum, please consider dropping me an email.
I'd be happy to pay €5-10 each for any book I don't yet own, and I'd also be more than happy to cover the cost of postage to Finland.
I'd be particularly pleased to see anything from Melbourne House, and while low-level is best, the coding-books from Usbourne (The Mystery Of Silver Mountain, etc, etc) wouldn't go amiss either.
I suspect most people who have collected and kept these wouldn't want to part with them, but just in case ..
0m 16k{rP _ _}
C3 03 EA 00 00 C3 06 DC 00 00 00 00 00 00 00 00
Numbers automatically get saved to the A-register, the accumulator. In addition to that there are three dedicated registers:
M-register is used to specify which RAM address to read/write from.
The instruction m copies the value of accumulator to the M-register.
The instruction M copies the value of the M-register to the accumulator.
K-register is used to execute loops.
The instruction k copies the value of accumulator to the K-register.
The instruction K copies the value of the K-register to the accumulator.
U-register is used to specify which port to run I/O input and output from.
The instruction u copies the value of accumulator to the U-register.
The instruction U copies the value of the U-register to the accumulator.
So the program above:
0m
0 is stored in the accumulator.
m copies the value of the accumulator to the M-register.
16k
16 is stored in the accumulator.
k copies the value of the accumulator (16) to the K-register, which is used for looping.
{ - Starts a loop.
The K-register is decremented by one.
If the K-register is greater than zero the body is executed, up to the closing brace.
Loop body:
r Read a byte to the accumulator from the address stored in the M-register, incrementing that register in the process.
P: Print the contents of the accumulator.
_ _ Print a space.
} End of the loop, and end of the program.
TLDR: Dump the first sixteen bytes of RAM, at address 0x0000, to the console.
Though this program allows delays, RAM read/write, I/O port input and output, as well as loops it's both kinda fun, and kinda pointless. I guess you could spend hours flashing lights and having other similar fun. But only if you're like me!
All told the code compiles down to about 800 bytes and uses less than ten bytes of RAM to store register-state. It could be smaller with some effort, but it was written a bit adhoc and I think I'm probably done now.
Recently I went through a burst of enthusiasm and started to overhaul the code a little, adding word-wrapping and fixing a couple of bugs. That lead to a new release, and also a brief amount of (positive) feedback on hacker news.
After mulling it over I realized that the number of CP/M BIOS functions I was using was very minimal, almost only the minimum you'd expect:
Write a character to STDOUT.
Write a $-terminated string to STDOUT.
Read a character from STDIN.
Read a line from STDIN.
It crossed my mind that implementing those syscalls should be trivial, and if I bundled implementations with a Z80 emulator library I'd have a means of running the game without a real CP/M installation, and without using the ZX Spectrum port.
After a day I had a working system, and I added a few more syscalls:
Open File, Create File, Delete File, Close File.
Console I/O.
Read Record.
After that? I can now play Zork 1, Zork 2, Zork 3, and The Hitchhiker's guide to the Galaxy, from Infocom.
I suspect I'm "done" for now, though it might be nice to add WriteRecord and the other missing functions there's no obvious use for yet another CP/M, especially with a CCP.
At the time it was capable of running the Infocom text-based adventure games, so I thought it was done. Of course I also wanted to run Microsoft's original BASIC and it turned out that was a challenge because the coding of their interpreter didn't use the standard CP/M entry-point for making syscalls (call 0x0005).
Instead of calling 0x0005 to invoke the BDOS/BIOS functions the BASIC interpreter used the single-byte CALL instructions which are available on the Z80 processor. There are a bunch of these instructions:
RST 00
RST 08
RST 10
RST 18
RST 20
RST 28
RST 30
RST 38
Each of those instructions is equivalent to a call instruction with a fixed offset, "call 0x0010", "call 0x0020", etc. I had to rework the emulator to cope with this approach, which causes repetition but nothing too surprising. The end result is that now my emulator can run Microsoft Basic, Tasty Basic, and some more programs.
Things work but a couple of the syscalls are of the form "Return true if there is a pending keystroke", or "wait until there is keyboard input present and return the first character". I have some busy-loops which peg the CPU, which sucks but works. On the downside running the code on a MacOS machine has some weird issues with repeated keys and similar. So I need to look into fixing that for my own sense of peace.
I put together a little repository of binaries for playing with though, and that's been helpful. My emulator has a special flag which treats sub-directories as "Drives". So A: points to A/, B: points to B/, etc. That makes distributing and working with things easy!
In my recent posts I've talked about implementing BDOS and BIOS syscalls for my cp/m emulator. I've now implemented enough of the calls that I can run many of the standard binaries:
The Aztech C Compiler
Microsoft BASIC
Turbo Pascal
Wordstar
etc
Of course I've not implemented all the syscalls, so the emulation isn't 100% perfect and many binaries won't run. But I sent myself on a detour by implementing extra syscalls, custom syscalls.
Traditionally CP/M systems are "rebooted" by pressing Ctrl-C at the CCP prompt. I thought that was something I'd press by accident so I implemented the restart behaviour only when the user pressed Ctrl-C twice in a row. But then I added a custom syscall that lets you change hte value:
A>ctrlc
The Ctrl-C count is currently set to 2
A>ctrlc 1
The Ctrl-C count is currently set to 1
A>
So you can now change the value at runtime. Similarly there is support for switching CCP at runtime, and even changing the default output-device from ADM-3A to ANSI, or vice-versa. It's kinda neat to make these kind of extensions, and happily the traditional BIOS has two syscalls reserved for custom use so I just used one of those.
I've added support for testing whether a binary is running under my emulator, or not, using a custom syscall. So I can run:
A>test
This binary is running under cpmulator:
cpmulator unreleased
https://github.com/skx/cpmulator/
On another emulator I see this:
A>test
Illegal BIOS call 31
No, this binary is not running under cpmulator.
Anyway I'm happy with the current state of things, and I fixed a couple of bugs which means I now have support for SUBMIT.COM which is a real time-saver.
My previous post mentioned that I'd added some custom syscalls to my CP/M emulator and that lead to some more updates, embedding a bunch of binaries within the emulator so that the settings can be tweaked at run-time, for example running:
!DEBUG 1
!CTRLC 1
!CCP ccpz
!CONSOLE adm-3a
Those embedded binaries show up on A: even if they're not in the pwd when you launch the emulator.
Other than the custom syscalls I've updated the real BDOS/BIOS syscalls a bit more, so that now I can run things like the Small C compiler, BBC BASIC, and more. (BBCBasic.com used to launch just fine, but it turned out that the SAVE/LOAD functions didn't work. Ooops!)
I think I've now reached a point where all the binaries I care about run, and barring issues I will slow down/stop development. I can run Turbo Pascal, WordStar, various BASIC interpreters, and I have a significantly improved understanding of how CP/M works - a key milestone in that understanding was getting SUBMIT.COM to execute, and understanding the split between the BDOS and the BIOS.
I'd kinda like to port CP/M to a new (Z80-based) system - but I don't have such a thing to hand, and I guess there's no real need for it. Perhaps I can say I'm "done" with retro stuff, and go back to playing Super Mario Bros (1985) with my boy!