<IMG SRC="pierotating.gif" WIDTH=80 HEIGHT=80 BORDER=0>

Introduction

Lesson 1
Lesson 2
Lesson 3
Lesson 4
Lesson 5
Lesson 6

TI-85 Page
Ash

Welcome to the third lesson of the Ash School! In this lesson you'll learn how to compare registers with each other and how to do loops. To know this you must also know a bit about flags and what they are for. I will also explain what the "famous" ROM_CALLs really are and some advantages and disadvantages with the use of ROM calls.

Contents

Flags - a short explanation

Flags are very important. If you want to know what happened in the last instruction for example, you must check how the flags are set. Most of the instructions executed by the processor will modify some or all of the flags. So, how many flags are there on a Z80 CPU, and what do they do? The Z80 has 6 flags (or 7 flags depending on how you count) but you will probably never use more than two, the Zero Flag and the Carry Flag.

A flag can either be set or reset, depending on the last instructions that changed that flag. That means that 6 flags only takes 6 bits which fits in one byte, and that is how they're stored. You may have noted why the A registers doesn't have a "brother"? It has, and that registers is called the flag register (F) and the register pair is logically called AF then, though you can never do anything with the pair except push and pop (explanation in the next lesson).

The Zero flag

The Z flag is used to indicated whether the value of a byte which has been computed, or is being transferred, is zero. It is also used with comparison instructions to indicate a match, and for other miscellaneous functions. For example, if you decrease a register (with DEC) and the registers reaches 0, the zero flag will be set. Note that if the registers doesn't reach zero, the flag will be reset. The Z flag will also be set or reset when using dec, inc, add and sub. Note that ld doesn't affect the Z flag. In fact, ld does never change any flag.

The Carry flag

The C flag is used to indicate whether an additon or subtraction operation has resulted in a carry (if an 8 bit addition results in a 9 bit answer) or a borrow (if the answer is negative). The C flag is also used as a ninth bit in the case of shift and rotate operations, which will be discussed in the next lesson. Remember that all arithemtic, shift and rotation operations will either set it or reset the C flag, depending on the result.

Some examples on how different instructions affect the flags

Instruction(s): Z flag C flag Explanation
ld a,185 add a,93 Reset Set The result isn't zero, and the addition results in a 9 bit answer (278d = 100010110b).
ld a,43 sub 43 Set Reset The result is zero and no borrow is needed.
ld c,73 - - None of the flags are affected.
ld e,0 dec e Reset - Dec (and inc) doesn't affect the C flag, even if a borrow was needed.
ld hl,$8E56 add hl,hl Reset Set The answer is to big, 17 bits, and doesn't fit in a 16 bit register.

Jumps and calls

So, know you know a bit about flags, but how to use them? There must be some instructions that checks the flag, else it would be pretty useless. Luckily, there are plenty of such instructions, and all of them are jump or call instructions. There are two kind of jump instructions, relative jumps and absolute jumps. The calls are always absolute. The relative jumps are much faster and a bit smaller, but the relative jumps has a limit: you can only jump 129 bytes forward and 126 bytes backwards. In most cases that is sufficient, and you should always try relative jumps until the compiler give you an error ("Relative branch to long" or something like that). Then you should change to absolute jumps. The calls are ordinary subroutine calls, and the instruction ret will return to where the call was. Of course you can jump and call without bothering about the flags.

Table over different jumps and calls
Z80 instr Explanation
jr condition,arg jr condition,arg A relative jump to arg if condition is true.
call condition,arg CALL_condition(arg) A subroutine call to arg if condition is true.

You need to know which conditions you can use. JP and CALL supports 8, but JR (which is the mosted used) only supports 4. They all support NZ (not zero), Z (zero), NC (not carry) and C (carry), the other four are normally not used so i wont mention them here. If you don't want any condition, just skip that part of the instruction (ex: jr JumpHere). A simple program example follows.

    ld a,12 sub c jr z,CIsTwelve . . CIsTwelve: . .
Hopefully, you can guess what this program does. First, A is loaded with 12 and then subtracted with C (whatever it contains). If C equals A, then the result will be zero and the Z flag will be set, and then a jump will be made to CIsTwelve, else the program will continue directly after jr z,ClsTwelve. Perhaps I should say that labels (like CIsTwelve) doesn't take memory, so you could make a label for every row (like early basic...) if you want, but that would only make your program harder to read.

Actually the RET instrcution also supports conditions (all 8), so you can use RET Z to return if the result of the last instruction was zero. As you will see later this can be very useful.

Comparing registers

In the above example, you subtracted two registers to check if they were equal, and if they were the Z flag was set. That's logical because when subtracting two equal numbers the result is zero. There is one drawback though: the contents of A will change, which isn't so fun most of the time. Then we use another instruction, cp (compare), which works exactly like sub, except that the answer isn't stored in A! The only thing that happens is that some flags are set (we are only interested in the Z and C flag though). Now that's very good! Now we can easily check if two registers contains the same value. We can also compare the A registers with a numerical value (cp works exactly like sub, and the limitations are the same).

But what if we want to check if the D register is greater than the A register? What will happen when we compare? The Z flag won't be set because the subtraction won't be zero, but if D is greater than A, then the answer would be negative, right? And what happens then? Yes, the C flag will be set! If D is equal or lesser than A, the C flag will be reset. A table below shows all cases.

Table showing the Z and C flags when cp A,D is exectued
Case Z flag C flag
A < D 0 1
A = D 1 0
A > D 0 0

How to do different kind of loops

All programs probably uses loops. You almost always want you program waiting for something or repeating some instructions while waiting on something to happen, for example a key press. In the last case, you have to compare the scancode with the key you're waiting for. Example below:

    WaitKey: call GET_KEY ; Calls a routine in the rom that gets the last key ; pressed and stores the scancode in A. ; If no key pressed since last call, A will be 0. ; Note that the routine doesn't wait for a key. cp G_EXIT ; Compares A with the scancode for the exit key jr nz,WaitKey ; If the answer isn't zero, jump back and check again.

Sometimes you know how many times the loop should be reapeting, for example when making a line with the width of 20 pixels. One simple way to do this is to have a counter which starts at 20, and at the end of the routine, you decrease it and check if it's zero (of course you can start from zero and increase, but then you have to check if the counter has reached 20 with the cp instructions which isn't needed when using dec, since dec will set the Z flag when the register has reached zero). But for this case the Z80 has a special instruction for us, which both decreases, sets the Z flag if necessary and makes a relative jump if the Z flag isn't set. That instruction is called djnz (Decrease, Jump not zero), and the syntax is djnz label (it's my favourite instructions btw, because it "looks" cool and is fun to pronounce :-).

But wait! How do we tell which register do decrease? Well, we don't. Djnz always decreases the B register, and that's why the B register is very often used as a counter. Of course another problem arises now, what if we want to loop, lets say, 1000 times? B is only a 8 bit registes so it won't work. One way is to use a 16 bit register and decrease it with dec, another is to nestled loops, but then you must save the B reg. Two different examples below:

 ld b,4
Loop1:
 ld c,b     ; push bc
 ld b,250
Loop2:
 .
 .
 djnz Loop2
 ld b,c     ; pop bc
 djnz Loop1
 ld bc,1000
Loop:
 .
 .
 dec bc
 jr nz,Loop

If you want to use the left version, you can use push and pop instead of using the C register. Of course, the right version is shorter and faster, but sometimes you may prefer the left version.

If you want to move memory from one location to another, there are some other useful instructions, ldir and lddr, but I'll explain them in another lesson.

ROM_CALLs, what are they?

Yeah, what is a rom call really? I've seen some people saying: "How do we do that? With a rom_call maybe?". That shows that people think they're powerful tools, and sure, they are, but you normally don't use too many of them. So, what happens?

What we do is that we use routines that already exists (why reinvent the wheel?), and that saves a lot of memory. For example, to display a string, you need a lot of instructions: you must take the first letter, look up the font, put out the correct character, and update the cursor. All this takes about 1500 bytes (font included) I guess, so it's rather smart to use a routine that's already done and is stored in the rom, wouldn't you say? Another thing: in the program above I used call GET_KEY. Observe that I used call, which means that I know exactly where the routine is stored, which means that the get key routine is at the same place in all ROM versions (yes, GET_KEY and some other calls in the ti-85.h are ROM calls though it doesn't "look" like one). That's not the case with D_ZT_STR (Display Zero-terminated Text String), because it's at different places in the ROM depending on the ROM version. Therefore, a call to Ash must be made which finds out where the absolute address is and then jumps to the right offset. In fact, all ROM_CALLs should be called ASH_CALLs, because first a call is made to Ash, then a jump is made to the ROM... The real ROM calls are GET_KEY and those... ;-)

To find out which ROM calls you should use, read ti82.h and 82-ROM.TXT which contains more details on how you call the instruction.





This page is maintained by Dines Justesen. All lessons are based on Jimmy Mårdels Online ZShell School.
(c)1999 Content: Dines Justesen and Jimmy Mårdel; Design, Graphics, Animation: mousewasher's WebDesign
These sites are optimised for IE4+ 800x600