The iLoad feature of the Z80-MBC2 homebrew Z80 computer allows uploading binary code that runs on the bare metal.
I thought it would be fun to try iLoad with some Intel 8080 code generated by the asm80
assembler of Suite8080, the suite of 8080 Assembly cross-development tools I'm writing in Python. But the code crashed the Z80-MBC2, uncovering a major asm80
bug.
It all started when, to practice the toolchain and process, I started with a Z80 Assembly demo that comes with the Z80-MBC2, which prints a message to the console and blinks one of the devices's LEDs. I assembled the program with the zasm Z80 and 8080 assembler, uploaded it via iLoad, and successfully ran it.
Next, I ported the blinking LED demo from Z80 to 8080 code and assembled it with asm80
. But when I ran the demo on the Z80-MBC2, it crashed the device.
The baffling crash left me stuck for threee months, as I had no tools for debugging on the bare metal and there were only a few vague clues.
I carefully studied the less than a hundred lines of code and they looked fine. To isolate the issue I cut the code in half, leaving the part that prints a message to the console, and transforming the blinking demo into this bare.asm
hello world for the bare metal:
OPCODE_PORT equ 01h
EXEC_WPORT equ 00h
TX_OPCODE equ 01h
EOS equ 00h
CR equ 0dh
LF equ 0ah
org 0h
jmp start
ds 16
stack:
start: lxi sp, start
lxi h, message
call puts
hlt
message: db CR, LF, 'Greetings from the bare metal', CR, LF, EOS
puts: push psw
push h
puts_loop: mov a, m
cpi EOS
jz puts_end
call putc
inx h
jmp puts_loop
puts_end: pop h
pop psw
ret
putc: push psw
mvi a, TX_OPCODE
out OPCODE_PORT
pop psw
out EXEC_WPORT
ret
end
The constants at the beginning define the addresses of the output ports, the opcode for sending a character over the serial line, and a couple of control characters. Next, the program sets up the stack and iterates over the output string to print every character.
The simplified demo program still crashed the Z80-MBC2, forcing me back to the drawing board.
Then I had an epiphany. What if the binary code asm80
generates is different from zasm
's?
I fired up the dis80
disassembler of Suite8080 and compared the output of the assemblers. Sure enough, the difference jumped at me: the destination addresses of all the branches after the label message
are off by 5 bytes.
The instructions branch to addresses 5 bytes lower, so the call to puts
executes random string data that chrashes the device. The last correct address asm80
outputs is that of the label message
. The address of the next one, puts
, is wrong and leads to the crash.
Indeed, the same demo code assembled with zasm
ran fine on the Z80-MBC2 and printed the expected message. This confirmed my hunch.
What now?
The next step is to find the bug in the Python source of asm80
, which I'm developing with Replit. Although Replit provides a debugger, I won't use it. The tool is not well documented and I'm not sure how it works. In addition the Replit debugger is best suited to code started from the run button. This is inconvenient for command line programs like the Python scripts of Suite8080.
Therefore, I'll take the opportunity to use Python's native debugger pdb
, which I always wanted to try in a real project. I played with pdb
a bit and it looks easy to use, with all the commands and options handy.
Let's see if pdb
can help me pinpoint the bug in the Python code.
#Suite8080 #z80mbc2 #Assembly #Python
Discuss...
Email | Reply @amoroso@fosstodon.org