Learn Multi platform eZ80 Assembly
Programming... For more power!
Introduction
The eZ80 is an enhanced version of the Z80... still technically an 8 bit
processor, it has a 24 bit address bus and extends the previously 16 bit
registers to 24 bit - this allows addressing up to 16mb memory.
There is a new 'ADL MEMORY mode' which works in full 24 bit
addressing.... 'Z80 Memory Mode' emulates a classic Z80.
As the eZ80 is an extension of the Z80, This tutorial assumes you
understand the basics of the Z80... if you do not, you need to start here.
 |
If
you
want to learn eZ80 get the Cheatsheet!
it has all the Z80 commands, and new enhanced commands the eZ80
adds to the cpu
|
 |
ChibiAkumas eZ80 tutorials
Z80 Hello World Series
Z80 Platform Specific Lessons
The new features of the eZ80
The Z80 is an 8 bit processor, usually around 4 MHz, but 6mhz
versions exist.
The MSX Turbo-R R800 is also 100% Z80 compatible, with 4x the effective
speed.
Each Register can only store one byte (0-255), but some registers can be
used in certain cases together as 16 bit pairs. For example HL together
are 16 bits, and can store 0-65535.
Each of the registers has a 'purpose' it is intended for., but you can use
any register for anything you want!
The different registers all have 'strengths' because many commands will
only work with certain ones, and some commands may be slower or need more
code if you use the wrong one.
The Z80�s large number of registers makes z80 programming very different
to a system like the 6502.
The Z80 uses a 16 bit address bus. This means it can address $0000-$FFFF
(0-65535) giving a total of 64k. Systems like the 128K Amstrad 6128 get
around this limitation via bank switching.
In addition to the addressable memory, the Z80 also addresses hardware
ports via OUT and IN.
On some systems like the MSX, ports are 8 bit. These use register C as the
port number in commands like "OUT (C),A" but on on other systems like the
CPC or Spectrum it�s 16 bit, using BC. Annoyingly the command is still the
rather misleading "OUT (C),A"!
You may wonder why some use 8 bit ports, and some use 16 bit ones, The
difference is not the Z80 itself, but how the Z80 is wired up in the
computer.
On the Z80 when commands have two parameters: The parameter on the right
is the source, the parameter on the left is the destination.
For example: "ADD HL,DE" will add the source DE to the destination HL.
Finally it should be noted the eZ80 is a more enhanced Z80 with a 24 bit
address bus, which is also backwards compatible with the Z80.
The eZ80
Registers
The eZ80 registers are an extension of the classic Z80, and it's main
registers have been upgraded to 24 bits.
The eZ80 has had all it's registers pairs extended - an 'upper' byte
(HLU/BCU/DEU) has been added... this can not be used on it's own - just
when HL/BC/DE is loaded in entirety by a command like "LD HL,$123456"
There are two stack pointers - one for ADL
mode (eZ80) and one for legacy mode (Z80 compatibility)... in Z80 legacy
mode MB (MBase) acts as the top byte of 24 bit addresses. so if MB=$EF
then "Call $1234" would effectively call to $EF1234
MBase can only be altered from eZ80 mode.
Main
Registers:
Register group
|
8 Bit Upper |
8 Bit High |
8 Bit Low |
Use cases |
A Reg |
x
|
x |
A |
Accumulator |
F Reg |
x
|
x |
F |
Flags |
BC Reg |
BCU |
B |
C |
Byte Count |
DE Reg |
DEU |
D |
E |
Destination |
HL Reg |
HLU |
H |
L |
Source / 24 bit accumulator |
IX
Index Reg |
IXU |
IXH |
IXL |
Base+Offset |
IY
Index Reg |
IYU |
IYH |
IYL |
Base+Offset |
Stack Pointer Long (24 bit) |
SPL |
Stack in eZ80 mode |
Stack Pointer Short (8/16 bit) |
MB |
SPS |
Stack in z80 mode |
MB / MBase |
x |
x |
MB |
Top byte of addresses in z80 mode |
Refresh |
x |
x |
R |
Used by Ram |
Interrupt Vector |
I |
IM2
Byte |
Used in Interrupt Mode 2 |
Program Counter |
PC |
Current running code |
|
|
Flags:
SZ-H-PNC
|
Name |
Meaning |
S |
Sign |
Positive / Negative
|
- |
|
|
Z |
Zero |
Zero Flag (0=zero)
|
- |
|
|
H |
Half Carry |
Used by DAA
|
- |
|
|
P / V |
Parity / Overflow |
Used if a sign changes because a register is too small
|
N |
Add / Subtract |
Used by DAA
|
C |
Carry |
Carry / Borrow
|
|
CPU Modes
Mode |
ADL |
MADL |
MBASE |
MMU |
Native Z80 |
0 |
0 |
0 |
off |
Virtual Z80 |
0 |
0 |
!=0 |
off |
Native Z180 |
0 |
0 |
0 |
on |
Virtual Z180 |
0 |
0 |
!=0 |
on |
ADL Mode |
1 |
|
doesn't matter |
doesn't matter |
Memory mapped registers
The MMU functionality is enabled and
configured by 3 memory mapped registers
Address
|
Purpose
|
Details
|
$000038 |
CBR - Common Bank Register |
Used by 80180 compatibility mode (Z180) |
$000039 |
BBR - Base Bank Register |
Used by 80180 compatibility mode (Z180) |
$00003A |
CBAR - Common Bank Area Register |
Used by 80180 compatibility mode (Z180) |
Whether we're in 'Short' Z80 (ADL=0) or
'Long' eZ80 (ADL=1) mode, there may be times we need a single command to
work in the other mode.
We can do this with suffixes to the command... these contain two parts...
the first part is the CPU mode (S or L for Short or Long)... the second
part is the Parameter mode (IS or IL for Short or Long)
We can specify both parts, or just one... for
example we can specify the complete "LD.LIL", or the shorter "LD.L" or
"LD.IL"... when the full size isn't specified, the missing part will be
'filled in' based on the current mode. (L / IL for eZ80 ADL=1
... S / IS for Z80 ADL=0)
LD.XiY
X = CPU mode (S / L) (CPU
Data block)
Y = Parameter length (IS /
IL) (CPU Control Block)
BC=$123456, HL=$ABCDEF,
MB=$89
|
CPU
Mode
|
Parameter
Mode
|
Command |
Result |
SIS |
Short
(Z80) |
Short
(16 bit) |
LD.SIS HL,$A2A3
LD.SIS (HL),BC
LD.SIS (HL),$B2B3 |
HL = $??A2A3 (U=$00 in ADL mode - Unknown
in z80 mode)
$89CDEF = $3456
($89CDEF) = $B2B3 |
SIL |
Short
(Z80) |
Long
(24 bit) |
LD.SIL HL,$A1A2A3
LD.SIL (HL),BC
LD.SIL (HL),$B1B2B3 |
HL = $??A2A3 (U=$00 in ADL mode - Unknown
in z80 mode)
$89CDEF = $3456
($89CDEF) = $B2B3 |
LIS |
Long
(eZ80) |
Short
(16 bit) |
LD.LIS HL,$A2A3
LD.LIS (HL),BC
LD.LIS (HL),$B2B3 |
HL = $??A2A3 (U=$00 in ADL mode - Unknown
in z80 mode)
$ABCDEF = $123456 (S
had no effect)
($ABCDEF) = $00B2B3 |
LIL |
Long
(eZ80) |
Long
(24 bit) |
LD.LIL HL,$A1A2A3
LD.LIL (HL),BC
LD.LIL (HL),$B1B2B3 |
HL=$A1A2A3
$ABCDEF = $123456
($ABCDEF) = $B1B2B3 |
Lesson
1 - 24 Bit ADL mode, and new commands
On the TI-84 we'll usually want to work in 24 bit mode "ADL"
mode.
In this lesson we'll learn how ADL changes the existing
commands, and what commands have been added over the classic
Z80. |
 |
 |
|
 |
The
eZ80 has two modes...
Classic Z80 mode where ADL=0... in this mode register pairs like
HL are 16 bit.
eZ80 mode (ADL Mode) is
the new mode where ADL=1.. in this mode register triples like
HL are 24 bit. |
 |
Load Commands
When eZ80 mode (ADL mode) is enabled, the previously 16 bit load
commands will work in 24 bits.
Being 8 bit, the function of the 8 bit
accumulator is unchanged.
Here we've loaded a 24 bit value into HL
and IX...
IY is also 24 bit, but of course we can
specify a 16 bit value if we prefer. |
 |
Here we loaded the 8 bit accumulator with
128 ($80)
We loaded the 24 bit HL, IX
and IY... Though we only specified a
16 bit value for IY. |
 |
Maths Commands
All previously 16 bit
commands are now 24 bit.
Here we've added 24 bit DE to 24 bit HL with "ADD
HL,DE"
This command now works in 24 bit, as does "SBC
HL,DE" and "DEC HL"
8 bit commands are still 8 bit, so "INC H"
has the same effect as before. |
 |
Here you can see the first three 24 bit commands affected all
the bytes in HL
The last "INC H" only altered the H
byte of the HL H L triple, making it roll round to 1
|
 |
In eZ80 ADL mode, The Push and Pop commands
will push and pop commands will transfer 3 bytes per command. We
use it here to back up and restore HL
Even PUSH/POP AF will push a third unused byte.
There is typically no way to work with the 8
bit top "HLU" byte of the 24 bit HL, but if we need to, we
can push it onto the stack and read it from there.
|
|
The stack pointer changed by 3 bytes. We
were able to back up and restore HL.
We also pushed HL onto the stack, then read HLU
off the stack into A
|
|
Loading from, and writing to ram
Loading from memory addresses has received an upgrade!
Loading to the accumulator works the same
as before,
But we can now load a 24 bit triple in a single
command! (this will work in 16 bit in ADL=0 mode)
This does not affect the loading of an 8 bit part
like E, D etc... there's still no way to load the upper byte :-(
These new commands even work with
(IX+n) addressing! |
 |
A and E were
loaded from HL... as were the three bytes in BC...
We then loaded HL from IX+4 |
 |
These commands work for storing to memory too!
We can store the Accumulator, a 24
bit triple, or an 8 bit part. |
 |
We stored 8 bit A, 24
bit BC and 8 bit D to
memory. |
 |
Multiply Sleep and Test
Z80 users rejoice! The eZ80
adds an 8 bit multiply command to the Z80!
It's usage is simple, we use a register pair, for example HL,
the result will be HL=H*L... it also works on BC,
DE and (Strangely) SP!
Note: the MLT command is always 16 bit, even in ADL mode.
As an added bonus, lets look at SLP...
this puts the CPU permanently to sleep, it's intended for power
saving (the internal clock still works) |
 |
|
The multiplications were performed, then SLP locked up the CPU!
|
|
|
We now have a TST command... this takes
an 8 bit register, and sets the flags like an AND command. the
registers are unchanged
This command compares to the accumulator... We cannot do "TST B,C"
|
|
|
Here we compared A and C... resulting in the Z
flag being cleared |
 |
|
Loading and Pushing Effective Addresses
Using Index registers IX and
IY with an offset is handy, but it can be slow, and takes extra
memory
The eZ80 gives us two new commands to work with 'Effective
addresses'.
LEA will load the 'Effective address' (The result of the index
register plus offset) in a 24 bit register (HL,BC or DE)
PEA will push the effective address onto the stack, where it can
be used later. |
 |
BC was loaded with IX+7... DE was loaded
with IX-1
IX+6 was pushed onto the stack... then popped
into HL |
 |
Strange new registers!
The eZ80 has a variety of
strange and upgraded registers.
First is the I register - it's only used in
Interrupt Mode 2 - it was 8 bit, but is now 16 bit, and is
loaded via HL.
in Classic Z80, ADL=0 mode we're only using 16 bit addresses, the
'top byte' of the actual physical 24 bit address is defined by a
register known as MB (MBase)
We can only set this in eZ80 ADL mode, and need to make sure it's
correct for when we use z80 mode.
Finally is the 'Mixed mode flag'... if we're combining eZ80
(ADL=1) and classic Z80 (ADL=0) code we need to set this with STMIX... if we are only using eZ80 code, we
should use RSMIX... this alters how
interrupts are handled by the processor |
 |
We loaded HL from I... then loaded
I from HL
next we loaded A from MB... then loaded MB
from A
We also messed with STMIX/RSMIX... but there's no way to read from
that flag! |
 |
Lesson
2 - Switching between 16 bit Z80 and 24 bit eZ80 (ADL)
Ideally we'd work in 24 bit ADL mode all the time - it's
certainly easier.
Unfortunately we do need to cover the complex issue of mixed
modes...
Brace yourself - it's going to get nasty!
|
 |
 |
|
 |
Switching Between Z80 and eZ80
 |
We're going
to use tons of command extensions to switch between Z80 and eZ80
mode on a per command basis!
If you need to see more details... you need to read this
section here
|
There are two stacks on the eZ80... the Z80
'Short stack' SPS and the ADL eZ80 'Long stack' SPL
When we switch between modes, these may both be used.
|
 |
We're going to do some tests of jumping to Z80 (Short) code and
eZ80/ADL (Long) mode
First we need to do some set up to make things work right, we need
to set up MB (MBase - the top byte of the Z80 mode program
counter)
We also load IX and IY with the stack addresses - this is for our
stack display code |
 |
We can call a Z80 subroutine with "CALL.IS"
This will switch to non ADL classic Z80 mode |
 |
We need to tell the assembler to start compiling classic Z80
mode, on WLA we use ".ASSUME ADL=0"
Typically all commands will be 8 bit, but we can override
these to force a command
If we want to call an eZ80 ADL subroutine, we need to use a long
call with "CALL.IL"
Because we specified a calll with a mode switch We need to use a
special return "RET.L" - this is the return
we use whether returning to Long Z80 ADL code, or Short classic
Z80 code |
 |
When we call an 8 bit subroutine the 24 bit
return address is split into two parts... the low 16 bit is
pushed onto the SPS stack... the top 8 bits is pushed onto the SPL
stack
When we call from eZ80 / ADL mode, a mode byte 03h
byte will be pushed onto the SPL stack... this will tell the
future RET.L command what state to return the processor to |
 |
We can call a Long subroutine from Z80 code with "CALL.IL"
This will switch to eZ80 / ADL mode... it will still need to
include "RET.L" at the end. |
|
We need to tell the assembler to compile eZ80 ADL code again.
We also need to use RET.L to read the byte off the stack when we
return |
 |
When calling eZ80 / ADL code from classic Z80 code, the 16
bit return address is pushed onto the SPL stack.
a mode byte 02h byte will be pushed onto the
SPL stack... this will tell the future RET.L command what state to
return the processor to |
 |
We can also use Jump commands to switch between Short Z80 and
Long ADL/eZ80 mode. |
 |
We can use these
commands irrespective of the starting mode... We can use CALL.IL
from either Z80 or eZ80 mode... the only important thing is that
we use RET.L to return.
As RET.L uses the 'Extra' mode byte on the stack, we cannot use
it with a regular CALL
|
 |
The many effects of CALLs
The call command will have differing effects
on the stacks depending on the mode before and after the call... here is a
summary of the affect on the two stacks.
Cpu
Mode
Before
|
Cpu
Mode
After
|
Suffix
|
SPS (16 bit)
|
SPL (24 bit)
|
New PC
|
S
|
S
|
none
|
PC.L, PC.H
|
|
MBASE, Addr.H, Addr.L
|
L
|
L
|
none
|
|
PC.L, PC.H, PC.U
|
Addr.U, Addr.H, Addr.L
|
S
|
S
|
.IS
|
PC.L, PC.H
|
02h
|
MBASE, Addr.H, Addr.L
|
L
|
S
|
.IS
|
PC.L, PC.H
|
03h, PC.U
|
MBASE, Addr.H, Addr.L
|
S
|
L
|
.IL
|
|
02h, PC.L, PC.H
|
Addr.U, Addr.H, Addr.L
|
L
|
L
|
.IL
|
|
03h, PC.L, PC.H, PC.U
|
Addr.U, Addr.H, Addr.L
|
Switching modes for a single
command (full specification)
We're in Short Z80 Mode There are
4 possible options if we can specify:
SIS, SIL, LIS
and LIL
Here are the 4 options applied to an LD rr,(IX) command |
 |
Here are the results
In this case SIS & SIL and LIS & LIL have the same
function |
 |
This time we'll use SIS, SIL,
LIS and LIL with
the LD (IX),rr command
Here are the 4 options applied to an LD rr,(IX) command
In this case SIS & SIL and LIS & LIL have the same
function |
 |
Here are the results |
 |
Because we specified the full specification, it doesn't matter
if our commands are run in eZ80 ADL mode, or Z80 mode, the result
will be the same,
Here's the result in Long eZ80 ADL
mode. |
 |
 |
Rather than
specify the full extension, we can specify half, and the rest
will be 'filled in' based on the current mode.
Lets see all the options...
|
Switching modes for a single
command (.IS and .IL)
This time we're only specifying the .IS and .IL part... This
means the remainder will be filled in by the current mode.
If we're in Short Z80 mode .IL or .IS will
effectively perform .SIL or .SIS |
 |
In this case, all the loads were effectively 8 Bit |
 |
Here are the save commands. |
 |
Once again the commands were all effectively 8 Bit |
 |
This time we'll try the commands in Long
eZ80 ADL... .IL or .IS will effectively perform
.LIL or .LIS
|
 |
in this case all the commands effectively worked in Long mode. |
 |
This time we'll do the save comands in long mode.
|
|
Once again, all the commands effectively worked in Long mode. |
 |
Switching modes for a single command (.S and .L)
This time we're only specifying the .IS and .IL part... This
means the remainder will be filled in by the current mode.
If we're in Short Z80 mode .S or .L will
effectively perform .SIS or .LIS |
 |
The .S version has loaded an 8 bit value,
The .L version has loaded a 16 bit value. |
 |
This time we'll use the store command in Short mode. |
 |
The .S version has saved an 8 bit value
The .L version has saved a 16 bit value. |
 |
This time we'll try the commands in Long
eZ80 ADL... .L or .S will effectively perform
.LIL or .SIL |
 |
Once again, the .S commands have transferred short values, and
the .L commands have transferred Long values. |
 |
Lets try again with a write command. |
 |
Once again
The .S version has saved an 8 bit value
The .L version has saved a 16 bit value. |
 |
 |
In most cases the suffix
hasn't made a difference between IS and IL suffixes, but it
depends on the MBASE, and the command.
To be honest though, it's really best to avoid the issue all
together, and avoid using suffixes, and in the rare case you need
them specify the full .LIL .SIS etc rather than the partial
specification. |
Lesson
3 - OUTS and INS on the eZ80
Some Z80 systems had 16 bit ports (using BC) but others used
only 8 bit ones, (using C)
The eZ80 standardizes on 16 bit ports, and even adds commands
for on chip I/O (where the top byte of the 16 bit port is $00xx
Lets take a look!
|
 |
 |
|
 |
 |
DO NOT RUN THESE ON
REAL HARDWARE! - these examples are intended for emulator use
only!
OUTing to your hardware could cause problems, hardware damage,
and your screen to
explode causing shards of glass to poke out your eyes!
Don't say I didn't warn you!
|
The
TI-84 causes a NMI to ROM functions whenever an OUT occurs -
which rather ruins our plans to play with ports!
However, there's a work around, we can switch to 8 bit mode and
write our own dummy NMI handler to play with ports... lets have
a go! |
 |
New Port Commands
In addition to all the previous commands, the
eZ80 adds some strange new commands!
Some use (DE) as the port, with BC as a 16
bit loop counter.
IN
|
Out
|
Port
|
Src/Dest
|
Actions After
|
Repeat Until
|
IN0 r,#
|
OUT0 (#),r
|
$00## |
r |
|
|
IND2
|
OUTD2
|
$BBCC |
(HL) |
DEC HL, DEC C, DEC B |
|
IND2R
|
OTD2R |
$DDEE |
(HL) |
DEC HL, DEC DE, DEC BC |
BC=0 |
INDM
|
OTDM
|
$00CC |
(HL) |
DEC HL, DEC C, DEC B |
|
INDMR
|
OTDMR |
$00CC |
(HL) |
DEC HL, DEC C, DEC B |
B=0 |
INDRX
|
OTDRX
|
$DDEE |
(HL) |
DEC HL, DEC BC |
BC=0 |
INI2
|
OUTI2 |
$BBCC |
(HL) |
INC HL, INC C, DEC B |
|
INI2R |
OTI2R
|
$DDEE |
(HL) |
INC HL, INC DE, DEC BC |
BC=0 |
INIM
|
OTIM
|
$00CC |
(HL) |
INC HL, INC C, DEC B |
|
INIRX
|
OTIRX
|
$DDEE |
(HL) |
INC HL,DEC BC |
BC=0 |
We'll only be trying a few port commands in
this episode, The TI-84 doesn't offer many devices we can
access, and some of these commands do strange things (like
reading from port BC, and DECrementing B and C separately)
|
 |
Making the TI-84 behave!
The TI-84 causes a NMI
interrupt every time we send data to a port, which we can't stop
in eZ80 ADL mode, as we can't change the interrupt handler at
&0066
We can however switch to Short mode, in which all the 64k
accessible memory is ram, and write our own dummy interrupt
handler (Just a RETI command)
Here's a simple test program, which will dim the backlight via
port &B024. |
 |
Our program has made the screen dim, it then returns to the OS,
though the screen stays dim! |
 |
OUT0 - Writing to port &00xx
OUT0 is a new command, it
uses 16 bit port &00CC - where CC is the C register.
This can be used to access the internal IO of the eZ80. Here we
use it to read and change the speed of the CPU |
 |
INI2R and IND2R - for 16 bit ranges
Like the old INIR and OTIR commands, we now have new commands
that use a 16 bit port (DE) , a 16/24 bit address (HL) and a 16
bit counter (BC).
Here we're using these commands to scan the keyboard range (Ports
$A000-$A040) and transfer these to memory |
 |
When we press a key we will see the data represented in the
range. |
 |
INDRX and INIRX - repeatedly read from one address.
There will be times we need
to read a lot of data to or from memory from a single port.
Here we're reading a sequence of data in from port $6001, and
writing them to memory.
INIRX will read BC bytes from port DE to address HL onwards |
 |
We're reading lots of data here, we'll see it change as the
counter does. |
 |
INIMR - Read from consecutive ports in the $00xx range
if we want to transfer a range of bytes from a range of ports
where the top byte is &00xx we wan use INIMR
This will transfer B bytes from port $00CC onwards to address HL
onwards
here we've transferred 16 bytes from port $0000-$0015 to address
$1000+ |
 |
We've loaded 16 bytes from the first 16 ports. |
 |