PokeMon, the eZ80 monitor Apr 2017

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…

Weblog © Jean-Claude Wippler. Generated by Hugo.