A Z8 TALKER AND HOST

Brad Rodriguez
T-Recursive Technology
Toronto, Ontario


 A. INTRODUCTION

    What do you do with a new, unfamiliar CPU, with no
    accompanying development tools?  Perhaps a board of your own
    design, with untested logic?  You need a program which will
    let you both exercise your hardware, and load and debug
    software.  You need a "debug monitor."

    But...without a debug monitor to debug your debug monitor,
    this program should be as utterly simple and obvious as
    possible.  Something you can get running with nothing more
    than a PROM burner.

    You'd need at least three capabilities: examine the target
    system's memory, alter its memory, and start a program at a
    given address.

    In Forth circles, such a rudimentary monitor is known as a
    "talker" program.  This article describes a talker program
    for the Zilog Z8.  In addition to the basic features, it has
    embellishments such as register access and a breakpoint
    facility.  The talker is small (under 300 bytes), easy to
    port to new processors, and easy to get running.  I've used
    it to debug wirewrap prototypes, to bring up Forth kernels,
    to develop and debug large assembly language programs, and
    to debug applications in the field.


 B. THE TALKER PROGRAM

    The secret to keeping the talker simple is to offload most
    of the work to a host computer.  So, there are really two
    programs involved.  The "talker" program runs on the target
    hardware, and communicates via a serial port with a "host"
    program running in a personal computer.  I'll discuss the
    talker program first.

    1. HISTORY AND DESIGN PHILOSOPHY

       I originally wrote the talker program to replace Zilog's
       debug monitor for the Super8.  Zilog's monitor program had
       several shortcomings:

            1. It was too big to be embedded in an application
                 program -- it occupied 12K of ROM, needed 1K of
                 RAM, and used many of the CPU registers.
            2. Because it needed RAM, it couldn't debug "RAM-less"
                 hardware (or the RAM memory decoding).
            3. Because it took over UART interrupts, and shut off
                 others, it couldn't debug interrupt routines.
            4. It couldn't be "polled" in the background while
                 other tasks were running.
            5. Its access to Super8 registers was flaky.
            6. During breakpoints, it was difficult to examine the
                 processor state.
            7. It couldn't run on a half-duplex serial line (which
                 is what much of our hardware had).

       I wanted a program which was more useful for debugging
       tricky Super8 code, and which I could embed in a final
       application.  The program should:

            1. use a minimum of ROM
            2. use a minimum of on-chip RAM (register file)
            3. use NO off-chip RAM, if possible
            4. use NO off-chip I/O, if possible
            5. use NO interrupts
            6. use half-duplex serial communications

       The simpler Z8 version described here uses only 300 bytes of
       ROM, 6 bytes of on-chip RAM, and the on-chip UART.  The
       half-duplex feature is disabled, but can easily be
       re-installed by editing two macros.


    2. BASIC OPERATION

       Figure 1 is the listing of the Z8 talker program.

       The program, TALKER, repeatedly calls the routine TALK to
       poll the UART for received characters, and to process them
       when received.  TALK was "factored out" as a separate
       subroutine so that it could be called periodically from an
       application program.  (Obviously we can't call TALKER, an
       infinite loop, while an application is running.)

       A received character is either a command, or a hex digit.
       To simplify conversion, the characters 0123456789:;<=>?
       (hex 30-3F) are used as the hex digits.

       The talker program maintains 4 "virtual registers":

            MDR:  memory data register, 1 byte
            MAR:  memory address register, 2 bytes
            MBR:  memory bank (page) register, 1 byte
            WKG:  working (scratch) register, 1 byte

       (A sixth byte is used to hold temporarily the contents of
       the Z8's RP register.)

       When a hex digit is received from the host program, it is
       shifted into the low 4 bits of MDR.  The previous low nybble
       is shifted up, and the previous high nybble is lost.  Thus,
       sending "23" from the host will set the MDR to 23 hex.

       The characters 20-2F hex are used for the commands.  The
       basic command set has these six commands:

           HEX ASCII  COMMAND

            2A   *   fetch byte from MAR address into MDR, send to
                      host, and increment MAR
            2B   +   store byte from MDR into MAR address, and
                      increment MAR
            2C   ,   copy MDR to low byte of MAR
            2D   -   copy MDR to high byte of MAR
            2E   .   copy MDR to MBR (memory page)
            2F   /   start program at MAR address

       The memory address and page are set by loading a byte into
       the MDR, then transferring it to the desired register.  So,
       to specify memory address 1234 in page 00, the host sends
       the nine characters

            0 0 .  1 2 -  3 4 ,

       The MDR is then free for data.  The host can send two more
       digits and then a "+" command to write data to memory.  Or,
       the host can send a "*" command to read data from memory.
       In this case, the talker program will send the data byte as
       two hex digits, using the characters 30-3F hex (as above).

       Note that the talker program ONLY transmits on request from
       the host.  In a half-duplex environment, the target will
       turn on its transmitter, send the two digits, and turn off
       its transmitter.  The host, of course, knows to turn off its
       transmitter until two digits have been received.

       The fetch and store operators autoincrement the address to
       make downloads, uploads, and memory dumps more efficient.
       For example, you can dump 16 bytes starting at address 1234
       with the command sequence

            1 2 - 3 4 , * * * * * * * * * * * * * * * *

       The "memory page" register was included to allow access to
       multiple address spaces.  The Z8 has 64K of "Code" memory,
       64K of "External" memory, and 256 bytes of on-chip RAM
       (register file), selected by page numbers 0, 1, and 2,
       respectively.  This mechanism could also be used for I/O
       space, bank-switched memory, or extended addressing in
       processors such as the 8088 or 64180.

       Special notes: when TALKER is entered, it sends an "M" (for
       "Monitor") over the serial line.  This is the most basic
       functional test of the hardware.

       Also, TALKER puts its own address on the return stack.  This
       means that you can use "go" to start a subroutine, and when
       the subroutine RETurns, the talker will be re-started.  This
       feature is VERY useful for debugging subroutines.

       These six functions are sufficient to do useful work.   When
       I start work with a new CPU, they are all I include in my
       first talker program.

    3. BREAKPOINTS

       Eventually, I'll decide that I really need breakpoint
       facilities.  Fortunately, they are easy to add.

       To set a breakpoint, a machine instruction is replaced with
       a CALL to a breakpoint routine.  This breakpoint routine
       saves the machine state, then enters the debug monitor.  The
       debug monitor must include a command to restore the machine
       state and resume execution (return from the CALL).

       In the talker, all you need to save are the flags and the
       return address, since the talker uses no other CPU
       resources.  (The six registers used by the talker are
       reserved for its exclusive use.)  Then you just enter
       TALKER.  The "resume" operation depends upon a bit of
       cleverness, described below.

       Four new commands implement the breakpoint facility:

           HEX ASCII  COMMAND

            26   &   set a machine language breakpoint at the MAR
                      address
            27   '   copy low byte of MAR to MDR, and send to host
            28   (   copy high byte of MAR to MDR, and send to host
            29   )   send the MDR to host

       The entry point for breakpoints is MBREAK.  When MBREAK is
       CALLed, the flags are copied into MDR, and the return
       address is popped into MAR.  (A similar entry point, IBREAK,
       pops the flags from the stack.  IBREAK can be entered as the
       result of an interrupt.)  MBREAK then sends a "*" to the
       host to signal that a breakpoint was encountered.

       The "go" command has been modified to copy the MDR to the
       flags register, before jumping to the MAR address.  This
       means that "go" is also the "resume" function.  (Note that
       the stack usage has been carefully arranged to allow this.)

       If any other monitor functions are to be used, the host
       program must first issue the commands:

            ) ( '

       to fetch the address and flags, and save them for later
       resumption of the application program:

            <high-adrs>  -  <low-adrs>  ,  <flags>  /

       Why is the "&" command is needed, since the host program
       could easily use "store memory" commands to build a
       breakpoint?  There are two reasons.  First, since I use many
       versions of this program, the host doesn't know where the
       MBREAK entry point is located.  Second, since I use this
       monitor for several CPUs, the host doesn't know what opcode
       to use.  Function 26 hex stores the right opcode and the
       right address for all CPUs.

       The host program is, however, responsible for saving the
       instruction overwritten by the breakpoint.  All of the CPUs
       I use have a 3-byte subroutine call, so the host program
       just needs to fetch 3 bytes from the breakpoint address and
       save them.  This approach allows any number of breakpoints,
       just by modifying the host program.

    4. FORTH BREAKPOINTS

       One of my main uses for this program is to bring up Forth
       kernels.  So, I've added two special kinds of breakpoint for
       Forth code:

           HEX ASCII  COMMAND

            24   $   set a Forth "thread" breakpoint at the MAR
                      address
            25   %   set a Forth "code field" breakpoint at the MAR
                      address

       The difference between these two is illustrated by a simple
       high-level Forth word:

            :  DOUBLE  DUP  +  ;

       For a direct-threaded Forth, this would appear in memory as:

       +-----------------+------------+------------+------------+
       |  CALL DOCOLON   | address of | address of | address of |
       |                 |    DUP     |     +      |     ;      |
       +-----------------+------------+------------+------------+
        
       (In an indirect-threaded Forth, the call to DOCOLON would be
       replaced by just the address of DOCOLON.)  Note that DOCOLON
       is executable machine code, but DUP, +, and ; are Forth
       words.

       A "code field" breakpoint is set by replacing the call to
       DOCOLON with a call to a breakpoint routine.  This causes a
       breakpoint whenever the word DOUBLE is entered, before any
       of its definition was executed.  This kind of breakpoint can
       be set in any Forth word.

       A "thread" breakpoint is set by replacing one of the
       following addresses with the address of a Forth breakpoint
       word.  This is simply a Forth CODE word which calls the
       breakpoint routine.  If a thread breakpoint were set at "+"
       above, the breakpoint would be taken after DUP was executed.
       Thread breakpoints can only be set in high-level Forth words
       (colon definitions).

       Of course, ordinary machine-language breakpoints can be set
       in CODE words.

       The Forth breakpoints are still experimental; I don't yet
       have the corresponding "resume" functions, and these
       commands are not included in the host program.

 C. THE HOST PROGRAM

    The host program is a set of Forth words which send messages
    to, and process messages from, the talker program.  The user
    works in the Forth environment, and sees the debugging
    functions as additional Forth commands.

    Figure 2 is a listing of the host program.  It is written in
    MPE PowerForth for the IBM PC, an 83-Standard Forth with the
    ONLY/ALSO vocabulary extension.  Certain functions, namely
    file access and screen color selection, are specific to this
    Forth implementation.  I hope that the conversion to other
    Forths is reasonably obvious.

    This is an excellent example of building a Forth application
    by "layering" successively higher levels of abstraction.
    Starting with the words to perform character I/O, you define
    primitive talker functions, then more useful operations,
    until you reach functions such as "alter memory" and
    "display breakpoint."

    Since the code is reasonably straightforward, and much of it
    is commented, I will present just an overview here.

    1. THE SERIAL I/O

       The basic serial I/O functions are ?TX, ?RX, (TX), (RX),
       TXON, and TXOFF.  The latter two deserve comment.  Several
       of my applications have only a bidirectional RS-485 serial
       port.  I've built an RS-232-to-RS-485 converter for my PC,
       using the RS-232 DTR line to control the direction of the
       RS-485 transceiver.  If you're not running half-duplex,
       these could be made null functions.

       PowerForth uses PC@ and PC! as "fetch byte from port" and
       "store byte to port", respectively.  If your Forth system
       does not have equivalents, you will have to write CODE words
       to do this.

       Some applications poll the TALK routine infrequently.  If
       characters are sent too rapidly to the target, some of them
       are lost, and nonsense results.  So, PACE allows the
       transmitter to be slowed in software.  This is a crude
       approach, and open-loop to boot, but it eliminates the need
       for the target to acknowledge every character.

       BAUDTABLE sets up the COM port for a fixed 4800 baud (as
       required by the Z8 program).  This would be easy to alter
       for variable baud rates.

       TERM is a rudimentary terminal program.  This is useful to
       see if the target system is responding at all.  Also, some
       of my application programs (such as Forth kernels) use the
       serial port for terminal I/O once they are started.  I
       prefer not to exit the host program in order to talk to
       them.

       The terminal program uses a different display color, to show
       when it's active.  The word COLOR uses a PowerForth variable
       CURR-ATTRIBS, and it will need to be changed for your Forth
       system.  If your Forth doesn't have an equivalent, I
       suggest:
                      : COLOR  DROP ;

    2. BASIC FUNCTIONS

       SPILL is used to clean extraneous characters out of the
       UART.  An earlier version of this program did not have this;
       whenever an extraneous character was received by the host,
       it remained forever "out of sync" with the target.  The
       program now SPILLs periodically, at times when it is not
       expecting data from the target.

       TXH and RXH send and receive bytes as pairs of hex digits.
       These are fundamental to almost every talker function.

       XADR (external address) uses TXH and the commands 2C and 2D
       hex, to send an address to the talker's MAR.  X@+ and X!+
       (external fetch/store with postincrement) implement the
       commands 2A and 2B hex, respectively.  And the words CMEM,
       EMEM, and REGS are Forth "defined words" which all set the
       memory bank register in the target.

       With these words, it is straightforward to implement words
       such as DUMP and FILL.

    3. BREAKPOINTS

       The breakpoint words allow up to ten breakpoints to be set
       in the target.  When a breakpoint is set, its address and
       the three bytes replaced by the CALL must be saved.  These
       are kept in BPARRAY.  When a breakpoint is not in use, the
       address in BPARRAY is set to zero.  (This means you can't
       set breakpoints at location zero...which is just as well,
       since those are interrupt vectors.)

       INITBP simply marks all of the breakpoints as "unused".

       BP0 through BP9 are the words which set breakpoints.  They
       use the common word BREAK.  If an existing breakpoint is
       being changed, the old three-byte code fragment must be
       restored first; this is done by -BREAK ("un-break").  Then
       !BREAK fetches the three bytes at the new location, and sets
       a breakpoint there.

       When breakpoints are set, the application program should be
       started with GO.  This uses AWAIT to listen for the "*" sent
       when a breakpoint is encountered; ?BREAK then fetches the
       flags and breakpoint address from the target, deduces which
       of the ten breakpoints was encountered, and displays a
       message.

    4. HEX FILE LOAD AND SAVE

       LOAD loads an Intel hex file into the target system;
       SAVE saves a given range of target memory in an Intel hex
       file.  These words are rather cryptic, but (I hope) easy to
       figure out.

       These are the only words in the host program which do file
       access; they will need to be rewritten for your Forth
       system.

 D. USING THE TALKER

    1. Your Z8 board must be running the talker program.

    2. The Z8's serial port must be connected to the PC's COM1
         port.  This may be either a full-duplex connection
         (separate wires for TXD and RXD), or a half-duplex
         RS-485 connection.  For the half-duplex connection you
         should use a 75176 or DS3695 transceiver, with
         direction controlled by the COM1 port's DTR line.

    3. The IBM PC must run the talker host program, ZTALK.FTH.

    The command set, in Figure 3, has been loosely modelled on
    the Zilog Super8 debug monitor.  All numbers are entered and
    displayed in hex.  You may put more than one command on a
    line.

    If the talker program fails to respond while the host is
    waiting for data, the host will halt.  This can happen if
    the target is dead and you attempt to do a DUMP or SET
    command -- the host will send commands to a dead target, and
    then wait for a reply.  Use ESC to escape this wait loop.

    If this happens in the midst of a dump, or at random
    intervals, check that your serial line is clean.  If you
    have embedded the TALK routine in an application, you may be
    polling TALK too infrequently.  Type SLOW in the host and
    see if the problem goes away.

    I almost always start a debugging session by entering TERM
    and then typing a couple of "*" characters.  This is the
    memory examine command.  If the talker is running, you will
    see a pair of characters each time you press "*".

    Eventually I plan to improve the host program by using the
    command 29 hex to verify that data appears correctly in the
    MDR register.  This would allow the host to automatically
    test for a "live" target, and to verify the serial line.
    But that's for another day...

 E. CONCLUSION

    I hope that you find this program, or a variant, useful in
    your work.  I have attempted to generalize the functions of
    this talker so that they can be used with any 8-bit CPU.
    The program can easily be extended for 16-bit CPUs and
    larger address spaces.  I'd be interested to hear of any of
    these.

    The Z8 and Forth source code can be found in the Forth
    Interest Group Roundtable on GEnie, in file Z8TALKER.ZIP.

    Credit for the technique of shifting data into MDR and MAR
    belongs to Dr. Don Schertz of Bradley University, who used
    it many years ago in an 8080 monitor program.  I had the
    good fortune to be one of his students at the time.

