Lets Make a Retro Game Ep 5: Starting Z80 Assemble

In this episode we are going to go over the basics of Z80 assembler. 

This will not be a full tutorial on Z80 assembler as that would require a series in it’s own right, but hopefully should give you a start so you can follow on with the rest of this series, whilst doing some learning from suggested resources.

Now while we are on suggested resources, for Z80 there is no better than “Programming The Z80” by Rodney Zaks (ISBN: 0-89588-047-4).

Memory and Registers

Programming is all about moving ones and zeroes from one location to another in the computer’s memory.

The Z80 processor, like most processors have two different ways to store information:

·         Registers – These are the processors internal memory locations that are used for doing calculations like addition and subtraction.  They are located inside the processor itself, so they are the quickest place for the processor to access information

·         Memory – So this is the actual Ram memory that the computer or console has, this is a lot larger, but is located outside the processor so more time is required to read and write something to a memory location.

Being an 8-bit processor the Z80’s standard registers store 8-bits of information i.e. they can each hold a value from 0-255.

The name of the 8-bit registers are:

·         A is also called the "accumulator". It is the primary register for arithmetic operations and accessing memory.

·         B is commonly used as an 8-bit counter.

·         C is used when you want to interface with hardware ports.

·         D is not normally used in its 8-bit form. Instead, it is used in conjuncture with E.

·         E is again, not used in its 8-bit form.

·         F is known as the flags. It is the one register you cannot mess with on the byte level.

·         H is another register not normally used in 8-bit form.

·         L is yet another register not normally used in 8-bit form.

·         I is the interrupt vector register.

·         R is the refresh register. Although it holds no specific purpose to the OS, it can be used to generate random numbers.

·         IXH The higher (first) byte of the IX register. Note that I is not the higher byte of IX. Combines with IXL to make the IX register.

·         IXL The lower (second) byte of the IX register. When combined with IXH these two registers make up the IX register.

·         IYH Again, this is the higher byte of the IY register. Note that IYH is different from both I and IXH. Combines with IYL to make the IY register.

·         IYL The lower byte of the IX register. Combines with IYH to make the IY register.

But unlike other 8-bit processors, the Z80 can also treat pairs of registers as a single 16-bit value i.e. they can each hold a value from 0-65535:

·         AF is not normally used because of the F, which is used to store flags.

·         BC is used by instructions and code sections that operate on streams of bytes as a byte counter. Is also used as a 16 bit counter.

·         DE holds the address of a memory location that is a destination.

·         HL The general 16 bit register, it's used pretty much everywhere you use 16 bit registers. It's most common uses are for 16 bit arithmetic and storing the addresses of stuff (strings, pictures, labels, etc.). Note that HL usually holds the original address while DE holds the destination address.

·         PC The program counter. It holds the point in memory that the processor is executing code from. No function can change PC except by actually jumping to a different location in memory.

·         SP The stack pointer. It holds the current address of the top of the stack.

·         IX is called an index register. It's use is similar to HL, but it's use should be limited as it has other purposes, and also runs slower than HL.

·         IY is another index register. It holds the location of the system flags and is used when you want to change a certain flag. For now, we won't do anything to it.

As you can see the Z80 actually has quite a lot of registers, all used for different things, which can get a little confusing. 

The number of registers actually makes it easier to do certain tasks than other chips with smaller instruction sets e.g. 6502.  But you have to know which ones you can use for different tasks, so if unsure just check online or in the referenced book.

Moving Things Around

Right hopefully your head is not spinning too hard after that very wordy section and we can get onto the more practical parts of this episode.

There is one instruction for moving something to and from memory or a register and it’s called:

LD <target>, <source>

·         <target> This is where the information will end up.

·         <source> This is where the information comes from.

In general terms each of the <target> or <source> fields can be either a register, a memory location or an actual direct value.

If it is a register then you just put the name of the register e.g.

LD A,5

This will put the value 5 in the accumulator.

But you can also specify a memory location e.g.

LD A,($C000)

This will get the contents of the memory location C000h and put it in the accumulator.

If you wanted to calculate the memory location, you can use some of the 16 bit registers e.g.

LD HL,$C000

LD A,(HL)

So this example does the same thing as our previous statement, but of course we can control the memory location by changing the value in HL.

Most of the statements so far can work the other way around as follows:

LD ($C000),A

Save the value in the accumulator in the memory location C000h.

LD HL,$C000

LD (HL),A

Save the value in the accumulator in the memory location indicated by the HL register.

There are lots of combinations here and you can do quite a few of them with most of the registers, but not all, so just check any of the online guides or the suggested book for more details.

Let’s Do Some Maths

Now to round out this episode, let’s do something with some numbers and do some basic maths.

All maths is based around addition, subtraction and manipulation of bits.  For this quick look we will do some simple addition and subtraction to get you started.

Addition

To add two 8-bit numbers together we have to use the accumulator as follows:

ADD A,<source>

<source> can be a direct value, another 8-bit register, or a memory location pointed to by HL e.g.

; setup our values

LD A,5

LD HL,$C000

LD (HL),A

LD A,0

LD B,5

 

; now do our maths

ADD A,5

ADD A,B

ADD A,(HL)

So in this example each ADD statement would in fact add 5 to the current value in the accumulator so at the end it would contain a value of 15.

Now if you want to do 16-bit maths, HL takes the part of the accumulator as follows:

LD HL,$C000

LD DE,$1000

ADD HL,DE

This would result in HL containing D000h.

Subtraction

To subtract one 8-bit number from another we have to use the accumulator as follows:

SUB 5

SUB B

SUB (HL)

Notice there is not mention of the accumulator, as this statement only works on the accumulator.  Less typing but can be a bit confusing.

Now to do the same thing for 16-bits we have to use a different statement as follows:

LD HL,$C000

LD DE,$1000

OR A

SBC HL,DE

This statement stands for ‘Subtract with Carry’ so I have added an extra statement, OR A, which will OR the value of the accumulator with itself, which does nothing to the value but does clear the carry flag.

This will result in HL containing B000h. 

SBC can also be used with the accumulator for 8-bit maths where you want to include the carry flag set by something else (beyond the scope of this article).

Increasing and Decreasing

One more type of adding and subtracting is to increment or decrement a value by one.  Now this of course could be done by just adding or subtracting the value one using the statements we have looked at above, but to save both instruction space and speed most processors have instructions to do this directly.

The Z80 versions work as follows:

INC <register>

INC (HL)

DEC <register>

DEC (HL)

So these will increment or decrement either a single register (8 or 16-bit) or a memory location pointed to by HL.

Once again this is not supported by all registers.