Out of the factory, the eZ80 is totally ignorant: it comes with 256 KB of
empty flash memory. This reads as $FF, which corresponds to the “RST 38h
“
instruction, a one-byte call to address $0038 (mixed $-vs-h notations are
used for hex numbers: “$
” in Forth, “h
” for Z80).
Left alone, the eZ80 will continuously execute this instruction, cycling through its address space since the stack pointer will be constantly decremented. This can be detected with a multimeter set to frequency measurement, if you’re interested: just probe some address pins.
The ZDI protocol built into the eZ80 lets us take control of the CPU, execute arbitrary instructions, and read/write memory, including flash memory.
One approach would be to implement a “flash eraser/uploader” on the Blue Pill, write all the Z80 assembly code, (somehow) convert it to machine code, upload this to the eZ80, and then run it. But that’s a bit of a slow cycle - the process of iteratively trying out new code would be rather lengthy, and it certainly won’t be correct on the first try.
A more flexible approach is to start off by writing a “monitor”: code which lets us examine and change memory, and try out little snippets of code with a much smaller granularity. That way, the details of how an eZ80 works can be explored and gradually lead to enough understanding to implement a full-blown BIOS for CP/M. Let’s call it “PokeMon”, just for the heck of it…
Forth is an excellent environment for creating such a monitor: all we need to do is define a number of words, and then use Forth’s interactive nature to give commands to the eZ80.
The
PokeMon
implementation is available on GitHub. It defines a slew of single-character
commands, which makes it very convenient to use. The most important one being
“?
“:
? PokeMon commands:
z - initialise ZDI (call this one before any other command)
v - display eZ80 chip version info
<addr> a - set 24-bit address: upper 8 to MBASE, lower 16 to PC
u - upper, shorthand for '$FFE000 a'
d - dump 128 bytes of memory at current address
s - show current CPU status (<run> when running)
r - register dump, shows main Z80 registers
b - break, stops Z80 execution
c - continue, resumes Z80 execution
x - hard reset, toggles the RESET pin
y - soft reset, sends a ZDI reset command
<byte> w - write a byte to current address and advance to next
<w0> .. <w7> m - multi-word write, writes 32 bytes at once
123 e - erase and unlock flash memory (the 123 arg is required)
t - enter terminal mode (reset Forth to exit this mode)
ok.
With these commands, we can now start to investigate and mess with the eZ80.
Here is a dump of memory addresses $000000 to $00007F, using the “d
” (dump)
command:
0 a d
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ok.
Note that the eZ80 has a 24-bit address space. In its default Z80 mode, the upper 8 bits are fixed, but this “MBASE” register can be adjusted through special extended Z80 instructions.
Here is an arbitrary un-initialised area in SRAM:
$100000 a d
BC FB CE 75 F3 AF FB 3C A7 DB B7 F2 8E DD 2C B4
[...etc...]
18 6E FF 7B A9 FF FE 96 8F 54 EF DA F1 2C 99 48 ok.
Here you can see that the same memory area appears in multiple locations, since our SRAM does not fully decode the 24-bit address space (these chips are only using 19..21 address lines):
$200000 a d
BC FB CE 75 F3 AF FB 3C A7 DB B7 F2 8E DD 2C B4
[...etc...]
18 6E FF 7B A9 FF FE 96 8F 54 EF DA F1 2C 99 48 ok.
The current processor status is shown with the “s
” command:
s <run> ok.
If we break execution (”b
”), the status changes, and we can examine the
registers (”r
”):
b s r zdi FA:0000 BC:0087 DE:BFBF HL:7C3E IX:FFFF IY:FDDF SP:5B14 PC:0038 ok.
Now the CPU is in “zdi” mode (stopped), and the register dump shows that the
processor was running at address $0038 - as expected, as it’s constantly
executing “rst 038h
” instructions.
The internal SRAM is initially mapped to $FFE000 and up (it can be set to other addresses):
$FFE000 a d
06 00 0E A5 3E 03 ED 79 0E C3 3E 80 ED 79 0E C0
[...etc...]
51 8C B4 40 14 31 D5 A0 0F 04 0A 26 E8 76 09 C5 ok.
And we can write to it, as you can see here (”u
” is shorthand, see PokeMon
commands above):
u $11 w $22 w $33 w ok.
u d
11 22 33 A5 3E 03 ED 79 0E C3 3E 80 ED 79 0E C0
[...etc...]
51 8C B4 40 14 31 D5 A0 0F 04 0A 26 E8 76 09 C5 ok.
Here’s how to send a message through eZ80’s serial port 1:
u
$06 w $00 w $0e w $a5 w $3e w $03 w $ed w $79 w $0e w $c3 w $3e w $80 w
$ed w $79 w $0e w $c0 w $3e w $1a w $ed w $79 w $0e w $c3 w $3e w $03 w
$ed w $79 w $0e w $c2 w $3e w $06 w $ed w $79 w $21 w $39 w $e0 w $7e w
$a7 w $28 w $10 w $0e w $c5 w $ed w $78 w $e6 w $20 w $28 w $f8 w $0e w
$c0 w $7e w $ed w $79 w $23 w $18 w $ec w $18 w $fe w $48 w $65 w $6c w
$6c w $6f w $20 w $77 w $6f w $72 w $6c w $64 w $21 w $0a w $0d w $00 w
u c
The Z80 assembly code corresponding to the above data can be found in asm/hello.asm.
But there’s a problem: PokeMon will need to read out the serial port
on the Blue Pill, to show that this is actually working. So “h
“
saves that code and briefly starts up a UART2 listener:
h Hello world!
ok.
b r FA:7400 BC:00C0 DE:BFBF HL:E047 IX:FFFF IY:FDDF SP:B766 PC:E037 ok.
The last instruction is an infinite loop, and indeed that’s where the PC is once the code runs.
A similar test can be created which erases flash, loads the test code there, and
proves that flash memory also works properly. The command to erase flash is
“123 e
” (the mandatory 123
arg prevents a single “e
” entered on the
command line from inadvertently erasing flash).
Writing to flash is very simple: as very last step, the “e
” command will
unlock flash memory. After that, flash memory writes will “stick”, and it will
be set to read-only again after a reset.
All this is great fun, and it’s good to see that things are working as expected – but it’s also incredibly tedious. We definitely need a better solution to bootstrap our way out of this…