Here's a quick guide to Z-80 assembly language. Think of it like C but with most of the features removed. Fundamentally you only have a few variables to work with:
unsigned char A;
unsigned short BC, DE, HL, IX, IY;
unsigned char memory[65536];
There are no structures, no blocks, no for or while loops and a sort of mangled if
. There is goto
which will jump to a label. Or you can call
a label which is like a subroutine but it takes no arguments and returns void. We can always build up more functionality from these pieces, but for simple programs the variables we have and a few pieces of memory[]
will be enough.
You can use =
for assignment but there are a lot of rules. For instance, all these are legal:
A = memory[26];
memory[738] = A;
A = memory[BC];
memory[DE] = A;
But you can't use any old expression like:
A = memory[BC + DE];
The short
variables can be assigned to and from memory[]
but since they're bigger than char
they are automatically split up. When you say memory[15] = BC;
it recognizes that it won't fit and does this on your behalf:
memory[15] = BC % 256;
memory[16] = BC / 256;
And to be useful, the opposite happens when you say BC = memory[15]
:
BC = memory[15] + memory[16] * 256;
To get even weirder, you can talk about these high and low parts of BC
independently as B
and C
. But you can't do B = memory[25]
. If you want that, you need to use A
as in intermediary:
A = memory[25];
B = A;
DE
can be addressed as D
and E
and HL
as H
and L
. But not IX
and IY
(well, not legally, but let's not get into that).
All of the variables can be incremented and decremented. A = A + 1
or A++
if you like. A = A - 1
. Same with BC
, DE
, HL
, IX
, B
, C
and so on.
Can you do D = D + 2
? No! You have to do D++; D++;
. When it comes to math, A
is special. You can add any unsigned character to it or a constant. Or subtract. There are some logical operations like &
, |
and ^
but let's not worry about those. You can also add or subtract any of our unsigned char
variables like D
or L
. But you can't add or subtract memory[]
. That also has to be done in pieces. Suppose you want A = A + memory[84]
:
B = A; // save A
A = memory[84];
A = A + B;
Yes, it can get tedious. One comes to appreciate compilers. You can also add and subtract from HL, IX and IY but in even more limited cases:
HL = HL + BC;
HL = HL + DE;
HL = HL + HL; // Same as HL = 2 * HL.
IX = IX + BC;
IX = IX + DE;
IX = IX + IX;
// And follow the same pattern for IY
What about multiplying and dividing? Nope, not supported. With loops and bit shift operators you can manage to simulate them, though. Or less efficiently you can do multiplication by repeated addition and division by repeated subtraction.
But what about loops, anyways? Well, you could fill memory with something like this:
HL = 9; // You can assign constants to all your variables, thankfully.
A = 0;
loop:
memory[HL] = A;
HL = HL + 1;
goto loop;
Did I mention that your program lives in memory[]
? No? Well, it does so that's a problem. If you only want to clear only 10 of memory[]
, how would you do that? Here's where the deranged if
comes in. You'd like to write something like this:
HL = 9;
A = 0;
B = 10;
loop:
memory[HL] = A;
HL++;
B = B - 1;
if (B == 0) goto loop
[ Ooops, that's a bug. Should be != 0, but pretend it was the right thing; I'm too lazy to retype this all. ]
But if (B == 0)
isn't something that is supported. Too complicated. But there is a goto loop if zero;
What does that mean? Well, every time you do math the processor remembers a few things about the result. One is if the result was equal to zero. As it turns out we can simply replace that if
with:
goto loop if zero;
And we'll do our loop 10 times. We can even generalize the code to, say, clear out a number of memory[]
entries as given by C
. How? By having B
count up and looping back as long as it isn't equal to C
yet. We can't do if (B == C)
, but we can subtract B
from C
and check on a zero result which is the same thing. Well, we can't do C = C - B
, only A can do that. The loop will look something like this:
loop:
memory[HL] = 0; // turns out we can do this
HL = HL + 1;
B = B + 1;
A = C;
A = A - B;
goto done if zero;
goto loop;
done:
Awkward, but it'll work. The designers have some mercy. You can goto
if the result is not zero:
A = A - B;
goto loop if not zero;
There are some other tests allowed like if the result is less than or greater than zero. But simple testing against zero can take us far.
Special cases abound. What if we accidentally wrote this:
loop:
memory[HL] = 0;
B = B - 1;
HL = HL + 1;
goto loop if not-zero;
Looks like a bug, doesn't it. Instead of doing B
iterations we'll just keep on going until HL
wraps around to 0. But it isn't so. When we add or subtract a short value like HL
it doesn't affect the zero/not-zero
condition. The code will work. In fact, this is considered a feature by the designers of the Z-80.
About the only other thing I'll mention is function calls or subroutines, if you will. Given how every little thing has to be spelled out you can see how even a simple function that multiplies a number by 4 would be nice. But there are no arguments and no return values. Can't say things like B = mult4(B);
Instead we just set up conventions where we decide what variables or memory locations are used specially to pass parameters and where the results go. The caller then as to figure it out. We might define a subroutine that multiplies B
by 4 and returns the result in A
:
mult4:
A = B;
A = A + A; // that is, B * 2
A = A + A; // B * 4, we're don!
return;
If we wan't to multiply D
by 4 we'd do this:
B = D;
mult4();
D = A;
Fair enough. We'd also have to remember that mult4() wiped out the value in A
so we better have saved it if we needed it. And for that matter we ourselves have to use up B
to make the call. If we needed those variables then we'd just have to find somewhere to stash them away in memory[]
.
memory[20] = A; // can't forget A
B = D;
mult4();
D = A;
A = memory[20]; // back to what it was.
So that's Z-80 assembly language programming. Oh, there's a bunch of other stuff. And you have do say everything with an accent, but if you look up information about Z-80 assembly language I'm sure you'll get the idea. Here are a few examples:
A = memory[4]; LD A,(4)
A = 3; LD A,3
memory[2] = A; LD (2),A
A = A + B; ADD A,B
B = B + 1; INC B
Good luck on your assignment.