Electric Adventures

Bringing you back to the 80's

Electric Adventures

Bringing you back to the 80's

Lets Make a Retro Game Ep 6: Z80 Loops, Conditions

In this episode we are going to cover a few more essential concepts in Z80 assembler.

Loops and Conditions

Any programming language needs a way that different code can be executed depending on conditions.

Most assemblers have a number of flags that signal that a particular event has occurred.

The Z80 gives us the following flags:

S                   – Signed Flag. Determines whether the accumulator ended up positive i.e. bit 7 of the accumulator is positive (0), or negative (1)

Z                   – Zero Flag. Determines whether the accumulator is zero or not

H                  – Half Carry Flag.  Is set when the least-significant nibble overflows

P/V              – Parity/Overflow. Either holds the results of parity or overflow.  It’s value depends on the instruction used.

N                  – Add/Subtract. Determines what the last instruction used on the accumulator was.  If it was add, the bit is reset (0).  If it was subtract, the bit is set (1)

C                   – Carry. Determines if there was an overflow or not.  Note that it checks for unsigned values.  The carry flag is also set if a subtraction results in a negative number.

These flags are stored in a special register call F (part of the 16-bit pair AF), that you don’t access directly.

The flags are set when you execute any of the commands that effect the A register (or HL when doing 16-bit maths).

e.g. if we want to check a value against what is stored in the A register we would use the CP command as follows:

CP 80

This would then set several of the flags depending on the current value in the A register.

The CP instruction takes the value specified (or in the specified register) and subtracts it from the value currently in the A register.  The contents of the A register are not harmed but the flags are set as if you had performed a subtraction.

Jumps

Jumps in Z80 change the program counter i.e. the statement being executed.  The jump can be a straight GOTO this memory location, but it can also include a condition based on a flag.

There are two types of jumps:

·       - Jump direct (JP)

·       - And jump relative (JR)

They are both very similar, especially when viewed from your assembly code, but it is best using ‘jump relative’ where possible as it is faster and uses less memory.  But you can only jump 127 bytes forward or backwards.  The best strategy is to use ‘jump relative’ in the first instance, and only change it to ‘jump’ (or reorganise your code) when the assembler complains.

Now adding a condition is fairly simple, you just add the name of the flag you want to test and a comma before the address e.g. to jump relative if Not Zero you would use

JR NZ, MYLABEL

MYLABEL represents another place in your assembly code, you could put an actual address, but most of the time you don’t need to worry about that i.e. we let the assembler work them out.

So to make this a little clearer, let’s work through an example.

Say we wanted to do a specific task 10 times, we could do it as follows:

; set the number of times we want to go around the loop
LD B,10
; here is the start of our loop
; marked by a label i.e. the spot we want to come back to
LOOP:
; This is where we would put the other things we want to
; do inside the loop
; Next we decrease or decrement our counter
DEC B
; This will set the (Z)ero flag if B reaches zero
; Now we do our jump with condition i.e. if B is not zero loop
JR NZ, LOOP

If the code inside this loop ends up being larger than 127 bytes the assembler will tell you by generating an error.  You only need to change the ‘JR’ to ‘JP’ and it will fix the issue.

Now there is actually a better way of doing this particular loop in Z80, and with Z80 you will find there are often several ways of doing the same thing.  For the level of code we will be using it doesn’t really matter which one to use, the most important thing is to make sure that you can understand what has been coded when you come back to it later i.e. performance and memory use, we don’t need to worry about at this stage.

But for the curious, Z80 has a rather helpful instruction called DJNZ, which stands for Decrement and Jump when Non Zero.  It only works with the B register and the jump is relative, so you need to keep that in mind, so the same example would work as follows:

; set the number of times we want to go around the loop
LD B,10
; mark the start of the loop
LOOP:
; This is where we put the other contents of the loop
; Now we decrement and jump when non-zero
DJNZ LOOP

As you can see for loops without too much code, it makes things much simpler and as a side benefit it is both easier to read and faster.


 

Bytes, Bits and Nibbles

We are almost done covering the Basics of Z80 programming, but as we are writing a game, it’s probably a good idea to cover Bits as our game will use pixels, which are usually represented by one or more bits stored in our systems memory.

Now a single location in memory can contain a Byte, which is made up of 8 bits as follows:

 

 

 

 

 

 

 

 

7

6

5

4

3

2

1

0

This allows each Byte to contain a number from 0 to 255, we make larger numbers by combining bytes together (a topic for another time).

One more term to know is that the two halves of a Byte i.e Bits 0-3 and 4-7 are known as Nibbles and each Nibble represents a number from 0-15 or 0-F in hexadecimal.

What is Hexadecimal You Say?

Humans have ten fingers and ten toes so it is little wonder that we count things by breaking them down into groups of ten, but at the end of a day all computers know about are 1’s and 0’s, which means most amounts end up being made up of powers of two combinations.

Hexadecimal is a way of easily expressing the numbers from 0-15 with one character as follows:

Decimal

Hexadecimal

0

0

1

1

2

2

3

3

4

4

5

5

6

6

7

7

8

8

9

9

10

A

11

B

12

C

13

D

14

E

15

F

So a single Hexadecimal digit represents one Nibble and thus you only need two characters to represent a Byte of computer memory.  This is why Hexadecimal is used a lot when writing assembly code.

Hopefully these last two episodes have given you all a start on understanding programming in assembly language, next we can get back to putting together our game logic.