[Icarus Productions] - CThek for main page] [Image]
team Projects Articles Features
The Z80 processor? Never heard of that one!
The Z80 is indeed a very old processor, I think it was first
released in 1977 (correct me if I'm wrong). But it is still used
in various hand-held calculators, and gaming consoles, as it is
quite robust, fast (a 6MHz Z80 CPU is quite a bit faster than a
6MHz Intel CPU) and furthermore it doesn't need too much power.
Where is the Z80 used?
I can only state a few examples: TI-8x calculators, Nintendo
Gameboy, the Sega Master System and the Sinclair Spectrum, to
name a few. I believe that is also used for various simple
hardware as controller.
Is the Z80 still being produced?
The Z80 is still in production, but you can't get the chip from
Zilog. You have to get it from other distributers, like this
one. They are really cheap, you can get the 5 MHz version for
about $2, the 20 MHz version for $5.
Do you know any good books on programming the Z80?
"Programming the Z80" by Rodnay Zaks is BY FAR the best book on
assembly language programming. There also exist some other
useful ones, like "Z80 assembly language subroutines" by Lance
A. Leventhal and Winthrop Saville. What's more, I suggest at
least getting an opcode table, so you can look up all possible
combinations while programming.
You might also find some information on the net.
An opcode - what's that?
The Z80 is able to perform various instructions. An instruction
specifies the operation to be performed by the microprocessor.
From a simplified standpoint, every instruction may be
represented as an opcode followed by an optional literl or
address field, comprising one or two words. The opcode specifies
the operation to be carried out.
What is a register?
For faster access, the CPU itself contains a few "variables",
so-called registers. All main operations, like math, etc. are
performed using these, as they can be accessed more quickly than
the main memory.
On the Z80, these registers are called: A, B, C, D, E, H, L (and
IX, IY, F, I, R, SP: see below), A = accumulator being the most
important one. As the Z80 is an 8-bit processor, these registers
are all 8-bit wide, this means they can hold any value from
0-255 (= 8 bit or one byte). However, to store larger numbers,
you can combine a few of them: BC, DE, HL. The result is a 16
bit register, having a range from 0-65535. Note that if you
modify for instance B, the upper byte from BC will be modified,
too: Given that BC is $467C. Now you execute the instruction:
ld B, $58 ;load $58 (hexadecimal) into B
Then B will be equal to $58, and BC will be $587C. Same's true
for DE and HL.
You didn't mention the IX, IY, F, I, R and SP registers.
These are special purpose registers. They can be only used (or
directly accessed) for few operations:
* The index registers IX and IY are mostly used as auxiliary
HL. They are both 16 bit registers where you can't modify
the upper/lower byte independently (without using special
code). The reason for their name is that they can be used
to index fields in a structure, like
ld IX, $8000 ;point IX to memory location at $8000
ld A, (IX+$10);load A with value of memory location
... ;at IX+$10 = $8010
This is of course comfortable in some cases, however, it is
suggested to avoid using the index registers, as the
execution is slower and one opcode needs more memory.
* The F (flag) register is perhaps the most important one of
those listed here. It contains the so-called flags, which
store certain conditions after executing opcodes. The flag
register is basically a normal 8 bit register, but each of
the eight bits represent a flag:
| S | Z | | H |P/V| N | C |
S ... sign flag (P = plus / M = minus)
Z ... zero flag (Z = zero flag set / NZ = not set)
H ... used internally
P ... parity (PE = even / PO = odd)
N ... used internally
C ... carry flag (C = carry flag set / NC = not set)
The S,Z,P and C flags can be checked using various
instructions, like the conditional calls (call nc, ...) or
jumps. The most important flags are the zero flag and the
carry flag. The zero flag indicates whether the result of
the last operation was zero (Z) or not (NZ). The carry flag
indicates an overflow (i.e. if you add 20 to 240, even
though the register only can handle numbers from 0-255, the
carry flag will be set (C)).
* I is the interrupt register. It's an 8 bit register that
contains the upperword of the interrupt table. (this is in
most cases uninteresting, since the OS would handle it,
like Usgard). This register is interrupt specific, you can
only load it with A (the accumulator) or load A from it.
* The R register is the memory refresh register. After each
instruction, it'll be incremented. It can be used as a poor
random number generator. Like the I register, it can only
be loaded with A or the other way round.
* The Stackpointer (SP) is a very important register, though
it's changed most of time indirectly by PUSHing/POPing
values to/from the stack.
How would you use S and P flags?
These two flags can only be checked using the CALL or the JP
instruction. The Sign flag is used to indicate whether the
result of the last operation has a positive or negative result
(that is, depending on the operation, the 7th bit or the 15th
bit set to 1).
The partiy/overflow flag performs two different functions.
Specific instructions will set or reset this flag depening on
the parity of the result, which is determined by counting the
total number of ones in the result. If this number is odd,
partiby bit will be set to 0, otherwise to 1. Parity is most
frequently used on blocks of characters. The parity bit is an
additional bit which is added to the seven-bit code representing
the character, in order to verify the integrity of data which
has been stored in a memory device. FOr example, if one bit in
the code representing the character has been changed by
accident, due to a malfunction in the memory device, then the
total number of ones in the seven-bit code will have changed.
This error can be detected using the parity flag.
Furthermore, the parity flag is also used as an overflow flag.
It detects the fact that, during an addition or subtraction, the
sign of the result is "accidentally" changed due to the overflow
of the result into the sign bit.
Finally, this bit is also used for two unrelated functions:
during the block transfer and search instructions, this flag is
used to detect whether the counter register BC has attained the
value 0.
How can you represent negative numbers?
Basically, the Z80 doesn't know real negative numbers. However,
you can regard the numbers from 0-255 also as numbers from -128
to 127. Take a look at the following example: We want to add a
number to A, which will result in an overflow. We'll see what
happens.
ld A, 10
add A, 254 ;overflow: carry set, A = 8
As you see, the result is 8, which would be practically the same
as if we would do a SUB 2. Generally speaking, this means that
X+(256+Y) = X-Y. Example: 14+(256+(-1)) = 14+255 = 13, which is
the same as 14-1. Of course, the compiler will accept code like
this:
ld A, 14
add A, -1
This is of course more comfortable. As you can see above,
negative 8 bit numbers reach from -128 to -1 to 127. This would
be, converted with the formula above, 128 (10000000b) to 255
(11111111b) to 127 (01111111b). If you read this carefully, you
should have noticed that negative numbers have bit 7 set. So bit
7 can also be referred to as "sign" bit.
The same is true for 16 bit numbers, where the 15th bit can be
treated as sign bit.
And what about decimal numbers?
You can handle decimal numbers on the Z80 in two ways:
* Fixed-point decimal numbers: divide a 16 bit (or more bits)
number into two sections: integer and fractional part.
Typically, you use the upper 8 bits as integer part and the
lower 8 bits as fractional part, resulting in an accuracy
of 1/256 = 0.0039. Addition and subtraction can be
performed just as normal, but after multiplication and
division, you have to correct the values again. (example:
8*8 would be $0800 * $0800 = $400000, you have to perform a
shift right by 8 bits to get the correct number $4000).
Fixed point numbers are typically faster, use them in
critical passages where you don't need very accurate
numbers.
* BCD numbers: this is an approach which can serve you with
as accurate numbers as you want, however, processing time
is slower and you need special routines. A BCD byte is
parted in two 4 bit parts, each representing a number from
0-9. A BCD number can consist of as many BCD bytes as you
want. Therefore, if you want to have a 20 digit number, you
need 10 BCD bytes and an additional byte specifying the
location of the decimal point. Mathematical operations are
more difficult to perform, however, the Z80 serves with the
DAA instructions, which will correct BCD numbers after
addition, subtraction, etc.
Use BCD if you need very huge or accurate numbers, but not
in time-consuming passages.
Can you explain the basic arithmetic operations in BCD?
Of course. The following functions for addition and subtraction
assume that both BCD numbersare stored with their least
significant digits at the lowest address. Both numbers must have
the same length. Multiplication and division are pretty
difficult to program in BCD, plus, they need about 520 bytes of
free memory space. I might cover them later, but I don't think
so.
* Addition:
Input: HL = base address of addend, DE = base address of
adder, B = length of numbers
Output: Addend replaced by addend plus adder
ld A, B
or A
ret z ;test whether length = 0
Loop:
ld A, (DE) ;get byte of adder
adc A, (HL) ;add it to addend, care for carry
daa ;convert to BCD-decimal
ld (HL), A ;save number back in addend
inc HL ;next number
inc DE
djnz Loop ;continue until all bytes summed
ret
* Subtraction:
Works nearly the same, but instead of the ADC, a SBC is
used.
Input: HL = base address of minued, DE = base address of
subtrahend, B = length of numbers
Output: Minuend replaced by minuend minus subtrahend
ld A, B
or A
ret z ;test whether length = 0
ex DE, HL ;exchange subtrahend and minuend
Loop:
ld A, (DE) ;get byte of minuend
sbc A, (HL) ;subtract byte of subtrahend
daa ;convert to BCD-decimal
ld (DE), A ;save number back in minuend
inc HL ;next number
inc DE
djnz Loop ;continue until all bytes summed
ret
What's the purpose of the stack?
The stack is used for two different things: first, for storing
temporary values(PUSHing/POPing) and to store the return address
of a calling program. Here's a little example:
... ;DE contains some important number
push DE ;save DE onto the stack, as we'll
... ;need it later on
... ;use DE for other stuff meanwhile
pop DE ;now we need it, load it from stack.
...
Notes: only 16 bit registers can be PUSHed/POPed: AF (A and
flags), BC, DE, HL, IX, IY. Also, note that PUSHing isn't equal
to saving. Here's a more precise example:
...
push DE ;write DE to memory pointed to by SP-2,
... ;decrement stack pointer by 2 bytes
...
push HL ;write HL to memory pointed to by SP-2,
... ;decrement SP by 2
;NOTE: if you would now do a pop DE, DE would actually contain
;the value of HL!
pop BC ;load BC with memory pointed to by SP,
... ;this is, in fact, the previous HL.
... ;After that, increment BC by 2
...
pop DE ;load DE with memory pointed to by SP,
... ;which is the same as the DE above.
... ;Increment SP by 2 after that.
This could be looked at like this:
DE -> (SP-2), SP = SP-2
HL -> (SP-2), SP = SP-2
BC <- (SP), SP = SP+2
DE <- (SP), SP = SP+2
How can I use the OR, AND & XOR instructions?
These instructions are used for bit manipulation. Before reading
the following example, look at the following tables. The first
number (in the tables, referred to as X) is on the Z80 always
the A register, the second number (table: Y) can be any register
or number. The tables only show what happens with each bit.
AND OR XOR
X Y X and Y X Y X or Y X Y X xor Y
1 1 1 1 1 1 1 1 0
1 0 0 1 0 1 1 0 1
0 1 0 0 1 1 0 1 1
0 0 0 0 0 0 0 0 0
;Examples for AND, OR, XOR.
;Using binary numbers to demonstrate effect.
ld A, 10010101b
and 00001111b ;after that, A = 00000101b
ld B, 11000000b
or B ;A = A or B = 11000101b
xor 11111111b ;A = A xor 11111111b = 00111010b
What about the shift instructions? (SLA, SRL, RRC, etc.)
Shifting is the process of moving the bits inside a byte to the
left or the right. Shifting left (SLA) will move bit 0 to bit 1,
bit 1 to 2, ... and bit 7 to carry. Bit 0 will be set to 0. When
shifting right, there are two modes: arithmetical (SRA) and
logical (SRL, being the commonly used one). Logical shifting
right is the opposite of shifting left, i.e. bit 7 -> bit 6,
etc. bit 0 -> carry, bit 7 will be set 0. Arithmetical shifting
nearly works like this, but bit 7 is preserved. This is useful
when working with negative numbers(see above), as the sign is
kept.
Often, shifting is used as a faster alternative to
multiplication by 2 or division by 2.
Rotation is practically the same as shifting, but the last bit
(i.e. 7 or 0) won't get "lost". The Z80 differs between 9-bit
rotation (RR - instructions) and 8-bit rotation (RRC -
instructions). While 9-bit rotation left will move the 7th bit
into the carry AND the carry into bit 0 (i.e. carry is the 9th
bit), 8-bit rotation will directly move the 7th bit into the 0th
bit and set the carry to this value. Rotation right works the
other way round, this means that 0th bit is stored in 7th bit /
carry.
;Examples for shift instructions
ld A, 00001000b
sla A ;now, A = 000100000b, Carry = 0
ld B, 10000000b
sla B ;B = 00000000b, Carry = 1
ld C, 00110010b
srl C ;C = 00011001b, Carry = 0
rrc C ;C = 10001100b, Carry = 1
How can I have a list of numbers?
This is a pretty simple task. First, you have to reserve as many
bytes as you need. For instance, if you need 10 numbers, each 1
byte, you reserve 10 bytes. Let's pretend that you got a label
called LIST, which contains 10 1-byte numbers.
LIST:
.db 10,9,8,7,6,5,4,3,2,1
Then, you need some function to access a certain number from
this list. The following function, GetElement, needs two inputs:
HL is a pointer to the list, and A is the index of the number
you want to retrieve. Upon return, A will contain the correct
number.
GetElement:
ld E, A ;load DE with the index
ld D, 0
add HL, DE ;add index to list pointer
ld A, (HL) ;get number out of list into A
ret
Pretty simple, isn't it? Now, you could add code to preserve
that A is too big, but this isn't too important usually. If you
wanted 2 byte numbers, you'd call the ADD HL, DE two times, and
load the number into a 16 bit register pair.
And what about matrices?
Matrices can be treated nearly equally to lists. However, it is
suggested to use formats where the width is 2^n (2, 4, 8, 16,
32, ...). This makes it more easy to write the matrix-GetElement
code.
In memory, the matrix is stored like a list of wdt*hgt elements:
there are hgt lines, each having wdt elements. You can see below
how a matrix can be stored.
Basically, you can access an element from a matrix using a
formula like this A = DE+(C*wdt+B), C being row, B column to
read, and DE pointer to the matrix.
The code is for matrices with a width of 16, it can be easily
modified though.
GetElement:
ld L, C ;get row
ld H, 0
add HL, HL ;*2
add HL, HL ;*4
add HL, HL ;*8
add HL, HL ;*16 -> HL = C * 16
add HL, DE ;add to matrix pointer
ld E, B ;get column
ld D, 0
add HL, DE
ld A, (HL) ;get element
ret
;Matrix stored like this:
.db 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ;row 1
.db 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ;row 2
.db ...
If you want to add a new question mail to Andreas Ess.
[Image]
Page last updated on Tue
Mar 23 15:45:38 1999 [E-mail us!][Top of page][Image]
[Image]
               (
geocities.com/tutorman_2000)