11

The Z80 was (except for a handful of tiny incompatibilities) a superset of the 8080, adding a number of new instructions as well as the alternate register set. It seems therefore that it must have at least slightly superior code density.

Has anyone ever, by looking at 8080 code modified to take advantage of the Z80, or just estimating based on familiarity with both instruction sets, or otherwise, come up with any measured or estimated figure for just how much better the code density was on the Z80?

The previous question on code density of 8-bit microprocessors links to a paper that gives figures for the 6502 and Z80, but not for the 8080.

  • 2
    Does this answer your question? What is the relative code density of 8-bit microprocessors? – Martin Maly Nov 13 '20 at 14:04
  • 2
    @MartinMaly No, that doesn't give figures for the 8080. – rwallace Nov 13 '20 at 14:07
  • 2
    I'm voting to not close this as a duplicate since it isn't one – OmarL Nov 13 '20 at 15:25
  • 1
    This answer to "Why do C compilers for the Z80 produce poor code?" won't answer your question directly, but it does give some insight on why some of the Z80 features (IX/IY in particular) aren't as useful as they may first appear for reducing code size, especially for compiled languages. – RETRAC Nov 13 '20 at 17:29
  • 1
    Just anecdotal, but I think I used Z80 instructions to make my ROM monitor fit in 2K bytes. Did the 8080 have conditional return instructions? – Mark Ransom Nov 14 '20 at 0:51
17

85%

A Z-80 program will be 15% smaller than an 8080 program.

To come up with this guess I took a reasonably tight Z-80 program, the TRS-80 4K Level 1 BASIC ROM and estimated the cost of replacing the Z-80 specific instructions with 8080 code. I'll get back to the limits of this methodology later but for now let me plow on with this example.

Of the 4096 bytes in the ROM, 3708 comprise 2100 Z-80 instructions. And 508 of those are not available on the 8080.

16 are DJNZ which can almost universally be replaced with DEC B; JP NZ at a cost of 2 bytes each.

248 are relative jumps (JR) which can be replaced with absolute jumps (JP) at a cost of 1 byte each.

The remaining 244 I figured would cost either 1 or 2 extra bytes to replace. At 1 byte each the total code size would be 3708 + 16 * 2 + 244 or 4232 bytes. At 2 bytes, 3708 + 16 * 2 + 244 * 2 or 4476 bytes.

Reducing 4232 bytes to 3708 is a 12% reduction. Reducing 4476 bytes to 3708 is a 17% reduction.

15% looks like a nice round number in between those two.

Sketching why one or two bytes

A good deal of the remaining Z-80 instructions were bit test, set and reset (the ED prefix opcodes). The 8080 can do something like those on the A register using AND and OR instructions. Here's an outline to replace them:

BIT  3,B     ; 2 bytes
...
LD   A,B
AND  8       ; 3 bytes

SET  7,C     ; 2 bytes
...
LD   A,C
OR   128
LD   C,A     ; 4 bytes

RES  4,H     ; 2 bytes
...
LD   A,H
AND  $EF
LD   H,A     ; 4 bytes

An average of 1.7 extra bytes. Of course, the A register may not be available for simple translation. Saving and restoring it would cost 2 more bytes and would be problematic with the BIT instruction which sets flags. But I'll stick with the 1 to 2 byte estimate. The value may not need to be immediately loaded back into the register and often a small amount of restructuring could eliminate the need to save A register.

Another number of instructions were under the ED prefix. The 3 prominent ones are LD (mem),DE, SBC HL,DE and LDIR.

LD (mem),DE can be done with two extra bytes in 8080:

LD    (mem),DE    ; 4 bytes
...
EX    DE,HL
LD    (mem),HL
EX    DE,HL       ; 5 bytes

You may be done with DE and HL and not have to exchange them back. Or in this example you only need one swap with a little trickery and there is no cost:

LD    HL,(mem1)
LD    DE,(mem2)    ; 7 bytes
...
LD    HL,(mem2)
EX    DE,HL
LD    HL,(mem1)    ; 7 bytes

SBC HL,DE is often just used to subtract the two registers without carry (the Z-80 does not have a SUB HL,DE instruction). Often the 3 byte sequence OR A; SBC HL,DE is used (OR A is a Z-80 idiom to clear the carry flag). And commonly it is used to wastefully subtract a constant. Consider:

LD    DE,1000
OR    A
SBC   HL,DE       ; 6 bytes
...
LD    DE,-1000
ADD   HL,DE       ; 4 bytes

Being a little more clever makes the 8080 code smaller and faster than the straightforward Z-80 solution.

To literally replace SBC HL,DE when can do:

LD    A,L
SBC   E
LD    L,A
LD    A,H
SBC   D
LD    H,A      ; 6 bytes

That's a 4 byte loss. However if you actually wanted to subtract without the initial carry then the replacement is still 6 bytes but the original is 3 bytes. And at this point we should also consider the king of code density: subroutines. If SBC HL,DE occurs frequently it can be replaced by a call to a subroutine that does just that at a cost of a single extra byte. Over a large program the fixed overhead of the subroutine will amortize down to nearly zero. And there's no extra cost on each instruction if the OR A; SBC HL,DE 3 byte idiom is used.

This becomes more clear for LDIR and other block moves - a two byte instruction which can always be done with a 3 byte call.

The example program uses 37 instructions that involve the IX register (and none for IY). The main power of the index registers is that they can do (almost) anything HL can do at the cost of a single extra byte. Or 2 bytes if they are used to access memory via a signed byte offset -- LD D,(IX+3) or LD (IY-2),E.

Here it becomes difficult to imagine directly replacing them. I will argue that in many cases HL can take over for them at little to no cost because programs tend to access memory sequentially. For example, consider:

LD    IX,mem
LD    B,(IX+4)
LD    C,(IX+5)
LD    D,(IX+6)
LD    E,(IX+7)     ; 15 bytes
...
LD    HL,mem+4
LD    B,(HL)
INC   HL
LD    C,(HL)
INC   HL
LD    D,(HL)
INC   HL
LD    E,(HL)        ; 10 bytes

The pure 8080 code is 5 bytes shorter. Of course, there are situations where it will be more difficult. Once again we may appeal to either programmer cleverness or subroutines to postulate a lower overhead.

The last two instructions are harder to reason about. EX AF,AF' and EXX switch to alternate register sets. In essence they give you an entire new bank of 8080 general purpose registers to work with. This is a boon to speed but we can once again use subroutines to reduce the overhead to a mere 2 bytes per use asymptotically.

EXX        ; 1 byte
...
CALL zexx  ; 3 bytes
...
zexx:  (subroutine left as an exercise for the reader)

In summary, I contend that in many cases there are replacements that cost only one or two extra bytes and in other cases subroutines can be used to make the cost per use at most 2 bytes and in large programs the fixed cost of a subroutine approaches zero.

And than isn't even considering the RST instructions which are single-byte CALL instructions though only 8 are available. Used strategically they can reduce the overhead even more.

Reasonable Objections

Only one program was examined.

If you buy the Z-80 instruction cost analysis then this is easily remedied by studying other programs. But the only question is what percentage of the program being Z-80 only is reasonable. The rest is just math. The BASIC ROM was 25% Z-80 only instructions.

Replacing instructions with subroutine calls is really expensive.

It sure is, but we're looking an density, not speed. And that's more than plenty to grasp.

This doesn't take into account programmer cleverness.

The chances are good that a clever programmer starting from scratch will produce a smaller 8080 program. However, with very minor exceptions that will also be a valid Z-80 program so it makes the question moot.

But what we'd really like to know is how much smaller an 8080 program could be if written in Z-80.

Yes, that would be nice to know. And useful. But that seems like a much harder question involving the skill of the programmer.

Can we just game the system by writing a Z-80 interpreter in 8080 and thus claim for any Z-80 program larger than twice the size of that interpreter the density is actually worse?

I don't see why not. Again, density, not efficiency is in question. It may seem silly, but consider any program which uses some data-driven or interpreted code for its operation. How exactly do you account for that? Pedantically we can only really fall back on Z-80 and 8080 instructions that are directly executed by the processor. But in general programming it is a legitimate and fruitful technique to reduce code size. It does, however, seem like a solution out of the bounds of the spirit of the question. I think it is important to keep in mind if you are looking at actual solutions and deserves mention as a complicating factor in quantifying the density question.

  • 4
    +1 Awesome Nice analysis – chthon Nov 15 '20 at 9:14
  • 1
    You start with a definitive and untrue statement: "85% A Z-80 program will be 15% smaller than an 8080 program." Though you use 'guess' etc. afterwards, that's your big headline. – TonyM Nov 15 '20 at 10:47
  • 6
    Huh, the headline seems alright to me. You only have to read a few words past it to see that it is an estimate, and the method by which it is obtained is clearly explained. – Wayne Conrad Nov 15 '20 at 14:24
  • 1
    @WayneConrad, or instead, put "it's an estimate" in the headline, rather than claim something...then unclaim it straight after, detailing how vague and subjective it all is. Hardly a big deal to get it right. – TonyM Nov 15 '20 at 15:11
  • 3
    The percentage up front is meant to be provocative. I'm hoping someone will go through additional program analysis to prove it wrong. Or at least angrily read on and get into the methodology. Plus I thought it would be amusing to propose such a specific answer to a practically unanswerable question. – George Phillips Nov 15 '20 at 23:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.