Update: Investigation into Z80 real-time debugging on the AVR

I am still awaiting the arrival of the Z80 (and 8052 SBC) printed circuit boards.  In the meantime, the FatFS portion is at a point that I would like to implement and debug on actual hardware, so until the PCB’s arrive, that portion of the project sits “on the back burner“, so to speak.

Currently, I am conducting further research into implementing a software solution to assist in real-time debugging of code on the Z80.  Previously, I had written about my thoughts on using a pre-written Z80 emulator to keep track of the state of the Z80 internal registers or to write my own.

To start, I investigated writing my own “shadow register” code.  I started with the Z80 lookup table from the DOS-based TASM (V3.2) assembler.  TASM uses a lookup table for the Z80 assembly process using a file named “TASM80.TAB”.  A copy can be found here.  In looking over the TASM80.TAB file, I noticed that it supports the “undocumented” opcode prefixes (extended opcode), which ends up with a total of 519 instructions.  I decided to modify TASM80.TAB slightly to to use as a template to create a data structure to be held in program memory that can be merged into my AVR assembly code using a series of “.dw” and “.db” directives, which ATMEL’s avrasm2.exe can comprehend.  avrasm2.exe is part of ATMEL’s AVR STUDIO series.  I’ll add that I have never taken the time to upgrade past V4.17, since it has met my needs thus far.

After some slight modifications, I wrote a Linux BASH script to read in each line from the TASM80.TAB file then extract each “field” into specific variables.  From there, I write the formatted output to a file, which contains the “.db” and “.dw” directives.   Below is the script I used:

# mk_z80table.sh
# Makes an Z80 instruction lookup table include file
# for use with AVR assembler

#! /bin/bash

DEBUG=0 # Set to "1" to enable debug
SOURCE="TASM80.TAB" # TASM source file
TABLE="Data_Z80table.inc"

if [ $DEBUG == 1 ] ; then
 echo "SOURCE = $SOURCE"
 echo "TABLE = $TABLE"
fi

rm -f ${TABLE}
echo ";----------------------------------------------------------" >> ${TABLE}
echo "; Z80 Instruction Lookup Table " >> ${TABLE}
echo "; Contains the OPCODE, #of bytes for the OPCODE and the " >> ${TABLE}
echo "; text sting of the assembly language statement. " >> ${TABLE}
echo ";----------------------------------------------------------" >> ${TABLE}

# INSTR ARG OPCODE BYTES
cat ${SOURCE} | while read "LINE" ; do
 INSTR="${LINE:0:4}"
 ARG="${LINE:5:9}"
 OPCODE="$(echo ${LINE:15:4} | sed 's/ //g')"
 BYTES="${LINE:20:1}"

if [ $DEBUG == 1 ] ; then
 echo "--------------------"
 echo "LINE = $LINE"
 echo "INSTR = ${#INSTR} $INSTR"
 echo "ARG = ${#ARG} $ARG"
 echo "OPCODE = ${#OPCODE} $OPCODE"
 echo "BYTES = ${#BYTES} $BYTES"
 fi

if [ ${#LINE} -gt 1 ] ; then
 echo ";----------------------------------" >> ${TABLE}
 echo "; $LINE" >> ${TABLE}
 if [ ${#OPCODE} == 2 ] ; then
 echo ".dw 0x${OPCODE}00" >> ${TABLE}
 else
 echo ".dw 0x${OPCODE:2:2}${OPCODE:0:2}" >> ${TABLE}
 fi
 echo ".db 0,${BYTES}" >> ${TABLE}
 echo ".db \"$INSTR $ARG\"" >> ${TABLE}
 fi
done < ${SOURCE}

if [ $DEBUG == 1 ] ; then
 less ${TABLE}
fi

An excerpt from the output file follows.  Since the table is held in program memory, it must be word-aligned and contain an even number of bytes, otherwise the assembler will pad the odd-byte with 0x00, which can cause errors in the code.  Each instruction contains a total of 18 bytes resulting in 9342 bytes being used. 1) The first entry (.dw) holds the 16-bit value of the opcode.  For “regular” opcodes, the high-byte is the opcode and the low-byte is 0x00.  For the extended opcodes, the high-byte contains the prefixed opcode and the low-byte contains the specific opcode.  2) The 2nd entry (.db) contains the number of bytes the instruction uses.  3) The 3rd and last entry contains a string describing the format of the  mnemonic and it’s arguments.  In cases where the instruction is an extended instruction, say using the IX register with an 8-bit offset, I filled in the offset with “dd” as a place holder with the intention of filling “dd” in with the offset as a hexadecimal number before printing it.

;----------------------------------------------------------
; Z80 Instruction Lookup Table 
; Contains the OPCODE, #of bytes for the OPCODE and the 
; text sting of the assembly language statement. 
;----------------------------------------------------------

; AND (HL) A6 1 NOP 1
.dw 0xA600
.db 0,1
.db "AND (HL) "
;----------------------------------
; AND (IX+dd) A6DD 3 ZIX 1
.dw 0xDDA6
.db 0,3
.db "AND (IX+dd) "
;----------------------------------
; AND (IY+dd) A6FD 3 ZIX 1
.dw 0xFDA6
.db 0,3
.db "AND (IY+dd) "
;----------------------------------
; AND A A7 1 NOP 1
.dw 0xA700
.db 0,1
.db "AND A "

After the opcode lookup table was generated, I set out to writing some code.  First, I needed some actual Z80 test code to draw from, so I extracted some of the code from the Z80 UART test program I wrote a few weeks ago.  After writing some code and seeing just how much of a chore it was going to be to essentially re-write a partial Z80 emulator, I decided to place this avenue on hold.  One of the reasons why is because although I can decode the opcodes and decipher each instruction and its operands, I do not have direct access to the Z80 flag register “F”.  In order to properly “shadow” that register, I would have to precisely determine the flags states based on the instruction just executed.  Thus, I decided to re-investigate the Z80 emulator library (Z80EMU) written by Lin Ke-Fong.  The GIT repository is here.

I was able to compile the Z80EMU successfully under Linux.  I ran the zextest test program and it completed without error.  I then set out to modify it for use with the AVR using avr-gcc and my own MAKEFILE.  After spending some time looking through the code to get a feel for how it is supposed to work, I attempted to compile under avr-gcc using my MAKEFILE and immediately started having some difficulties with avr-gcc spitting out  “right [or left] shift count >= width of type” errors. I searched the Internet for some clues as to what that meant and found out that it had to do with the AVR’s default integer size being different than that of INTEL (or AMD), which this library was written to compile and run on.  I had a “doh!” moment at that time and realized that I had to modify the variable declarations from “int” to “uint8_t” and “uint16_t”.  That fixed some of those errors.  Eventually, I was able to get the Z80EMU code to compile for the AVR.  I also realized that there were data structures residing in RAM that really needed to be moved into program memory.  After making the necessary modifications, I could see the effect.  All that was left in RAM was a “memory” buffer for the code to get dumped into and the volatile Z80 registers.  When finished, the compilation succeeded without errors or warnings and the result was:

  text  data  bss   dec   hex filename
 13730     0  287  14017 36c1 z80emu.elf

Now I have to figure out just how to pass off the Z80 code to the emulator and retrieve the results.  Also, just because the library compiled does not mean that it actually functions.  So until I write some interface routines and test it, I will not know for sure.

Time for a break…

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s