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.
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
||The result isn't zero, and the addition results in a 9 bit answer (278d = 100010110b).
||The result is zero and no borrow is needed.
||None of the flags are affected.
||Dec (and inc) doesn't affect the C flag, even if a borrow was needed.
||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
||A relative jump to arg if condition is true.
||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.
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.
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
|A < D
|A = D
|A > D
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:
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 c,b ; push bc
ld b,c ; pop bc
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
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