• Please review our updated Terms and Rules here

[8080] Back-converting Z80 -> 8080 ... index registers

clueless_engineer (Brett)

Experienced Member
Joined
Nov 10, 2023
Messages
75
Location
Port Macquarie, Australia
Hello,

I'm trying to back-convert, if that's the word, the original Sargon Z80 code to 8080 as a way to learn assembly. There's lot of 8080 -> Z80 but not so much the other way.

I know it has been done (March-April 1979 issue of Recreational Computing alludes to it, using macros to replace Z80-specific mnemonics) but I've not seen any source code.

In particular, I'm curious how to convert instructions that use the Z80 index registers IX and IY that don't exist on the 8080.

I'm using the Z80 source here rather than the TDL code in the book.

This example, in which we initialise the 10x12 board (120 bytes), is where I'm stuck at the moment. I'm assuming we're going to have to do a bit more manipulation than since {LD IX} instructions.

INITBD: LD b,120 ; Pre-fill board with -1's
LD hl,BOARDA
back01: LD (hl),-1
INC hl
DJNZ back01
LD b,8
LD ix,BOARDA
IB2: LD a,(ix-8) ; Fill non-border squares
LD (ix+21),a ; White pieces
SET 7,a ; Change to black
LD (ix+91),a ; Black pieces
LD (ix+31),PAWN ; White Pawns
LD (ix+81),BPAWN ; Black Pawns
LD (ix+41),0 ; Empty squares
etc.
 
I did a bunch of it in my Turbo Pascal for 8080 project.

Your code may look like this when converted:

Code:
 E000                     00001 BOARDA  equ     0E000h
                          00002
 0001                     00003 PAWN    equ     1
 0002                     00004 BPAWN   equ     2
                          00005
 0100                     00006         org     100h
                          00007
 0100 06 78           [7] 00008 INITBD: mvi     B,120           ; Pre-fill board with -1's
 0102 21 E000        [10] 00009         lxi     H,BOARDA
                          00010
 0105 36 FF          [10] 00011 BACK01: mvi     M,0FFh          ; -1
 0107 23              [5] 00012         inx     H
 0108 05              [5] 00013         dcr     B
 0109 C2 0105        [10] 00014         jnz     BACK01
                          00015
 010C 06 08           [7] 00016         mvi     B,8
                          00017
 010E 21 DFF8        [10] 00018 IB2:    lxi     H,BOARDA-8      ; LD    a,(ix-8) ; Fill non-border squares
 0111 7E              [7] 00019         mov     A,M
                          00020
 0112 21 E015        [10] 00021         lxi     H,BOARDA+21     ; LD    (ix+21),a       ; White pieces
 0115 77              [7] 00022         mov     M,A
                          00023
 0116 F6 80           [7] 00024         ori     80h             ; SET   7,a ; Change to black
                          00025
 0118 21 E05B        [10] 00026         lxi     H,BOARDA+91     ; LD    (ix+91),a       ; Black pieces
 011B 77              [7] 00027         mov     M,A
                          00028
 011C 21 E01F        [10] 00029         lxi     H,BOARDA+31     ; LD    (ix+31),PAWN    ; White Pawns
 011F 36 01          [10] 00030         mvi     M,PAWN
                          00031
 0121 21 E01F        [10] 00032         lxi     H,BOARDA+31     ; LD    (ix+81),BPAWN   ; Black Pawns
 0124 36 02          [10] 00033         mvi     M,BPAWN
                          00034
 0126 21 E029        [10] 00035         lxi     H,BOARDA+41     ; LD    (ix+41),0       ; Empty squares
 0129 36 00          [10] 00036         mvi     M,0
                          00037
                          00038         end
 
I did a bunch of it in my Turbo Pascal for 8080 project.
Oh, brilliant, thanks @BillGee ... that's exactly what I was hoping for ... even just one example would've been a great help. That should be enough to prybar my brain loose.

Also, I didn't think of that ... writing a similar board initialisation in a higher-level language, compile and then disassemble to see how the compiler implemented it!
 
Also, I didn't think of that ... writing a similar board initialisation in a higher-level language, compile and then disassemble to see how the compiler implemented it!
I did not do that. I wrote a parallel of your code by hand.

By my Turbo Pascal project, I wanted a way to generate 8080 programs using Turbo Pascal which only makes programs for the Z80.

I talk about it here: https://forum.vcfed.org/index.php?threads/turbo-pascal-can-it-be-set-up-to-compile-into-8080.77676/

I had to rewrite the run-time library in 8080 code.
 
Most of the code that exists for the Dazzzler was written in Z80 , but I run it in my SOL-20 so I'm stuck with 8080.

For a simple program like Doodle, it was easy to convert the code from Z80 to 8080, and @daver2 wrote a routine to replace the Z80's LDIR instruction and that worked.

It could be possible that every instruction that exists in the Z80 could be re-written for the 8080, but I think for some of the more complex ones it would slow the program down a lot, likely needing a lot of pushing and popping to save registers, which might not matter in many cases. It surprized me looking at the Z80's instruction set and registers just how much more advanced it is than the 8080.

It could be an idea to make a book which shows the source code conversion routine for every single Z80 instruction in a form that would work for 8080. If there was such a book I would buy it.
 
It could be possible that every instruction that exists in the Z80 could be re-written for the 8080, but I think for some of the more complex ones it would slow the program down a lot, likely needing a lot of pushing and popping to save registers, which might not matter in many cases. It surprized me looking at the Z80's instruction set and registers just how much more advanced it is than the 8080.
The original Sargon Z80 was originally converted to 8080 by using macros to replace the Z80-specific commands, apparently ... according to a 1979 article. Yes, it would slow things down ... but for a chess program it's not as important as, say, an arcade game. But still, you'd want to optimise as much as possible ... which is something also covered in that 1979 article.
It could be an idea to make a book which shows the source code conversion routine for every single Z80 instruction in a form that would work for 8080. If there was such a book I would buy it.
Oh, I'd love that as well - it's a good idea. I've found loads of info about going 8080 -> Z80 (obviously) but very little for going back the other way. There's quite a few Soviet machines that use the 8080, hence my interest to create a version of Sargon for them (well, the Radio 86RK based machines).

I am doing a YouTube video about it though.
 
How easy it is to migrate from Z80 to 8080 depends mainly on how the code uses the Z80 instructions. Some complex instructions (like LDIR or OTIR) can be rewritten easily, at a modest speed cost. The snippet by BillGee is very efficient because the Z80 code sets IX to a constant value and does not use HL while using it. So HL is available in the 8080 case.

But when Z80 code uses arithmetic on HL, IX and IY to keep multiple pointers alive at the same time, things can get complicated easily. You need to make sure that the stack can handle the additional load, especially when the second register set is used a lot. Rewriting sections of code to be more 8080-friendly may be far better than a mechanical translation in these cases. If Z80 code uses the I or R registers (e.g. to generate random numbers), you may be out of luck and need to find a different solution. The same holds true if the additional registers are used for function parameters etc.

One saving grace is that most Z80 instructions are longer and slower than the basic 8080 instructions, so efficient code tends to avoid them in general.

It could be an idea to make a book which shows the source code conversion routine for every single Z80 instruction in a form that would work for 8080.
I'm not sure that such a book is useful. The Z80 finally died this summer, and a mechanical approach doesn't work in general.
 
  • Like
Reactions: cjs
You can also 'emulate' the Z80 complex instructions.

Think of a 1 byte restart instruction...

If you place that 1 byte restart instruction before the Z80 code that you wish to emulate, the 8080 processor will invoke the restart instruction. You need to save away the CPU registers on entry to the restart instruction, and restore them before returning. Then write a subroutine to emulate the specific Z80 instruction following the restart instruction. Don't forget that some of the instructions may modify the CPU registers. In this case, you would modify the stored registers before restoring them.

The Z80/8080 instructions will then run natively (no code size increase and no performance hit). The 'emulated' Z80 instructions will have a 1-byte restart instruction size overhead plus whatever the CPU overhead is for the emulation of the instruction. The code size increase will also have the overhead of the emulator instructions.

There could be a 'half-way house' by using a MACRO to expand some instructions (e.g. DJNZ, JR etc.) and emulation for the more awkward instructions.

The latter is the approach I would consider taking.

I once coded the FLEX 6800 operating system up on a Z80 using the MACRO method. Basically using the 6800 instructions as MACROS. The code size just ballooned. It did work though - just not memory efficient...

Dave
 
Last edited:
Also note that the flags are different between 8080 and Z80. Undocumented flags on the 8080 are hardwired. If you have software which relies on the F register values (such as Z80ALL or 8080EXM), they won't work correctly. Not much you can do.

But again, for simple cases and with source code, simple code changes will usually work. Some things can get tricky and really hard cases may be unsolvable.
 
Z88DK-Z80ASM does part of the work transparently. Just specify -m8080 or -m8085,.
I just completed a reverse engineering of Sargon 2.1 if you are interested.
My original goal was to adapt it to 'ALTCPM' systems, like the Lifeboat CP/M for TRS80 Model I and Heath H8/H89, but it turned out it's a really interesting study case, if you look into GitHub you'll find an improved version for the VIC 20 which borrows the openings dictionary from the CP/M version ;)

By the way the optimisations matter, sargon relies on time consuming algorithms
 
I once coded the FLEX 6800 operating system up on a Z80 using the MACRO method. Basically using the 6800 instructions as MACROS. The code size just ballooned. It did work though - just not memory efficient...
That sounds painful.
 
It is possible is to convert from z80 to 486, if the original program logic is not too peeky.
The only missing equivalent is the conditional RET, but it is easily replaced with a conditional JMP over a RET
 
It is possible is to convert from z80 to 486, if the original program logic is not too peeky.
Well, x86 is almost a perfect superset of Z80, so that is not really surprising. Converting Z80 -> 8080 is harder, due to lack of index registers, but since the Z80-extended instructions are slower, a lot of code still translates easily.

But try going Z80 -> 6502 (or even m68k/ARM), and things get messier.
 
Back
Top