CP/M program compatibility on my eZ80

136 views
Skip to first unread message

Dean Netherton

unread,
Jun 16, 2026, 9:17:27 PM (7 days ago) Jun 16
to retro-comp
Hello fellow retroists,

Having recently got CP/M booting directly on my eZ80 for RC kit, I wanted/needed a way to run CP/M programs that directly access hardware.  Programs designed for standard RomWBW based RC2014/RCBus systems with a stock Z80 CPU - that assume 8-bit I/O addressing and expect Wayne's HBIOS BIOS (RST 8) functions.

This is where the system boots directly from storage - without the typical 512K RAM/ROM module running RomWBW.  Its loads CP/M into a section of its linear memory address and implements a CBIOS for the eZ80's and associated hardware.

As there is no RomWBW - then there is no HBIOS - so I needed to create a compatibility layer - a set of minimal equivalent HBIOS functions for various features (such as timers and uart etc)

I wanted to be able to run various RomWBW distributed applications (eg: WDATE, TIMER, TUNE and others...)

WDATE and TIMER only needed me to implement the required HBIOS calls.  I can now run those programs directly - no recompile -- they just work.

But for TUNE.COM - I was only able to get it to work with the --HBIOS argument - where it directs PTx streams to the HBIOS sound driver.  Running it this way works - but its limited to just the 3 standard audio channels - no noise channel support.

To get full compatibility, I needed to wrap the execution of the TUNE program - and fully interpret the binary - translating all I/O operations to the appropriate platform range of $FFxx.  I had to write a Z80 emulator to run on the eZ80. (Zilog all the way down!)  I got a version committed to my repo yesterday, and now I can do the following CP/M command and play any tune targetting any hardware:

EMU TUNE ITERATN.PT3 --MSX

EMU.COM is a Z80 emulator program running on the eZ80 CPU.  It's a full Z80 instruction set emulator - with all I/O forceably mapped to the range $FFxx - it also ignores unimplemented opcodes, preventing the eZ80's rebooting when it encounters unimplemented opcodes.

The emulator switches between native and interpreted code.  When the program makes any BDOS/HBIOS call, the system switches back to native execution - so all OS and HBIOS calls run natively on the CPU - only the program's code is executed under the interpreter.

Its obviously slower - but for the TUNE example - its still more than fast enough to play at the correct speed.

It was a learning experience trying to create this EMU program (I had previously implemented the Z80 interpreter).  It was not too complicated, but rather fiddly - re-hooking the BDOS (CALL 5), WARM BOOT (CALL 0), and HBIOS calls to be intercepted - relocating code below BDOS - restoring the hooks on exit etc - and correctly parsing the command line (I did not know that the CCP parses the command line extracting filenames into the default FCBs - now I do).

It needs more testing and more features are need to be completed/added.

Cheers
Dean

j...@nmysbh.co.uk

unread,
Jun 17, 2026, 1:46:03 AM (6 days ago) Jun 17
to retro-comp
Wow! That's some seriously impressive work "just" to run TUNE.COM.

Well done!

Jon

On 17 Jun 2026, at 02:17, Dean Netherton <dean.ne...@gmail.com> wrote:

Hello fellow retroists,
--
You received this message because you are subscribed to the Google Groups "retro-comp" group.
To unsubscribe from this group and stop receiving emails from it, send an email to retro-comp+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/retro-comp/5dd2e5ae-6e4f-4015-853d-9d151f60882en%40googlegroups.com.

Dean Netherton

unread,
Jun 17, 2026, 2:49:19 AM (6 days ago) Jun 17
to retro-comp
Gee thanks mate - It was fun writing and learning how to make it work.  

And armm - yep - I guess it was a lot of work to just run TUNE.COM.  now that you make me think about it - oh boy am I wasting my life doing this????  ...... nah - this is totally normal!  😉

Dean

Wayne Warthen

unread,
Jun 18, 2026, 1:19:16 PM (5 days ago) Jun 18
to retro-comp
Very impressive work Dean!

-Wayne

Alan Cox

unread,
Jun 18, 2026, 3:39:54 PM (5 days ago) Jun 18
to Wayne Warthen, retro-comp
You can handle the unimplemented opcodes on both Z180 and eZ80 directly. The trap/0 jump in both cases has mechanisms that allow you to figure out what caused it and then emulate whatever Z80 naughtiness was used.

Doesn't solve the I/O port problem of course.

Alan

Dean Netherton

unread,
Jun 19, 2026, 3:42:37 AM (4 days ago) Jun 19
to retro-comp
Thanks mate

Dean Netherton

unread,
Jun 19, 2026, 3:59:43 AM (4 days ago) Jun 19
to retro-comp
Hi Alan,


> You can handle the unimplemented opcodes on both Z180 and eZ80 directly. The trap/0 jump in both cases has mechanisms that allow you to figure out what caused it and then emulate whatever Z80 naughtiness was used.

Yep - although this will only trap unimplemented instructions for the eZ80 -- as there are new instructions - any legacy code that has 'dodgy' bytes could end up doing weird things - and as you say - its not really helpful for the I/O issue.

Nonetheless I did use this 'feature' to allow native code to switch to the emulator context -- I effectively created 2 new instructions:

$CB $30 -- switches to the emulator (only if encountered when eZ80 was running in Z80 mode)
$CB $31 -- switch back to native.

Of course, if legacy Z80 code happened to have some dodgy bytes like this - it may end up breaking out of the emulator incorrectly.   But I did need a way to break out, so that CP/M's BDOS/BIOS calls can run be natively.

I did find it challenging in the trap 0 handler, to work out the trap reason.  Was the system just powering on/resetting? - or did it encounter an unimplemented code?  I found that determination was not super easy.

Dean.

Bill McMullen

unread,
Jun 19, 2026, 11:46:23 AM (4 days ago) Jun 19
to retro-comp
> I did find it challenging in the trap 0 handler, to work out the trap reason.  Was the system just powering on/resetting? - or did it encounter an unimplemented code?  I found that determination was not super easy.

I faced the same issue with the eZ80 but found a simple solution.  A reset (power-on or pushbutton) always initializes many of the I/O registers to a known state.  The GPIO DDRs (Data Direction Registers) are set to all inputs after a reset or a value of 0FFh.  However my initialization code sets certain bits to output, such as for LEDs.  Thus all that is required is IN0 A,(P?_DDR) : INC A : JR NZ TRAP.  I'm sure you can find lots of similar examples with other I/O registers.

I found that reliably detecting a power-on vs pushbutton reset is more difficult without external hardware.  I chose to use a four byte flag field at the beginning of ADL memory which is outside CP/M's Z80 mode area.  At reset, if this field doesn't hold my unique data pattern then I assume it's power-on and then initialize the pattern whereas if the pattern already exists then I assume it's a pushbutton reset.  An errant Z80 mode program shouldn't be able to access that flag field and my ADL code shouldn't be using it for any other purpose.  It's not foolproof but so far this has worked reliably for me.
Reply all
Reply to author
Forward
0 new messages