Just because we have CP/M ported to the eZ80, does not mean we can run all ‘CP/M’ dot COM program.
If the program is a pure CP/M program - that is - it makes no assumptions of the hardware and performs all access via the Operating System calls (BDOS), then it should be fine.
But if the program attempts to access hardware device directly, eg a sound chip, then it’s unlikely to work. This is due to the fundamental approach the eZ80 has with regards to accessing hardware devices. The differences between the standard Z80 and the eZ80 has been noted in previous posts on this site, but let’s do a quick refresher.
There are 3 main differences between the CPUs:
Some legacy CP/M programs may, perhaps inadvertently, cause the CPU to encounter unimplemented opcodes - byte sequences that do not correspond to any Z80 instruction. When this happens on a Z80, the Z80 will just ignore the bytes and continue. The programmer may not even realise this has happened, as perhaps, no apparent bug was produced in the program’s operation. (Some cleaver programmers may even use this for specific meta programming tricks!).
If the eZ80 encounters such unimplemented opcodes, it will reboot the system.
All the Zilog CPUs technically address the ‘I/O ports’ of hardware devices using a 16 bit address range. But to keep things simple and cheap, many solutions were designed to ignored the upper 8 bits of this address range. They would only use the lower 8 bits. With this 8 bit address range, we have a maximum of 256 I/O ports - which is more than enough for most old 8 bit systems to map out a large set of hardware peripherals.
The eZ80 CPU operates the same way, that is, its I/O ports are addressed with a 16 bit address range. But we can no longer ‘ignore’ the upper 8 bit address range when addressing I/O ports. This is due to the inclusion of on-chip hardware devices (UARTS, SPI, I2C, Timers, etc) that are mapped to a reserved address range of $0000 to $00FF.
For example, on the eZ80F92 version of the eZ80, the on-chip UART device has been assigned the 16 bit address of $00C0. So, say we have a sound chip wired to the address at $FFC0 and we run a legacy CP/M program for this sound chip. It will attempt to communicate with the sound chip by setting the lower 8 bits of the address to $C0, leaving the value for the upper 8 bits effectively random. As such, 3 things might happen:
So how do we get our CP/M programs that communicate directly with hardware or have these unimplemented opcodes to work?
One option, if we have access to the source code, is to update and reassemble the program to always set the correct upper address range.
But that’s no good if we don’t have access the code or the time to update it.
To resolve this, I wrote a special CP/M program to run other CP/M programs and resolve these compatibility issues. It’s called EMU.COM (Emulator).
The EMU program will interpret the bytes of a legacy CP/M program (much like a Z80 emulator on your PC would do). As each byte of the program is interpreted, we can resolve all the compatiblity issues. Thus, ensuring all I/O is mapped to the correct address and ignoring the unimplemented opcodes.
The EMU program switches back to native execution when the program makes a call to the Operating System - so all Operating System code is executed natively - with only the CP/M program’s code interpreted. The same applies if the program makes a RomWBW HBIOS16 call.
Of course, the downside is the program is executed at a much slower speed. (With an eZ80 @ 20Mhz, we achieve similar performace to a Z80 running at around 2 to 3Mhz).
At the time of writing, I have only tested this with the RomWBW distributed program TUNE.COM. With the EMU program, I can run TUNE.COM targetting an AY-3-8910 based audio module directly.
The following command will run the TUNE.COM program under ‘emulation’ allowing it to talk directly to the expected hardware:
EMU TUNE ITERATN.PT3 --MSX
The EMU.COM and associated support, is included in releases from 2026-06-16