Dec 052017
 
Ughbug v. 1.01 Intel 8031/8051 monitor/emulator. Version 1.00 appeared as an article in the July 86 Byte. Contains the source,the .HEX file and the first draft to the Byte article as documentation. Uploaded by author, G
File UGHBUG.ZIP from The Programmer’s Corner in
Category Assembly Language
Ughbug v. 1.01 Intel 8031/8051 monitor/emulator. Version 1.00 appeared as an article in the July 86 Byte. Contains the source,the .HEX file and the first draft to the Byte article as documentation. Uploaded by author, G
File Name File Size Zip Size Zip Type
EXAMPLE.D 1024 523 deflated
EXAMPLE.DI 512 275 deflated
FIG.2 2432 257 deflated
LISTING.1 896 292 deflated
LISTING.2 256 64 deflated
TPCREAD.ME 199 165 deflated
UGHBUG.ASM 31710 8868 deflated
UGHBUG.DOC 19951 7529 deflated
UGHBUG.HEX 11053 3896 deflated
UGHBUG.PRN 80994 16351 deflated

Download File UGHBUG.ZIP Here

Contents of the UGHBUG.DOC file


FIRST DRAFT OF BYTE ARTICLE
----- ----- -- ---- -------

Debugging a microprocessor-based project in assembly language can be
difficult for a home engineer. The equipment to aid such a task,
logic analyzers and emulators, is usually far too expensive. In the
case of emulators, because of their processor-specific nature, even a
commercial project may not be able to afford the thousands of dollars
needed for one. Such was the case when I was developing firmware for
an embedded processor controlling a commercial product. The
processor in use was an Intel 8031. At that time, only Intel had an
emulator for the 8031. Obviously, they wanted a lot of money for it.
Worse, it needed a new microprocessor development system to host it.

The alternative was to edit, assemble, burn EPROMS, trace the
execution using a logic analyzer, and deduce where the code was going
wrong. Once you found a bug, you usually had to start over at the
edit step before you could hunt for the next one. Needless to say,
this was a very slow process.

A BETTER WAY

It didn't take very long to become frustrated with this state of
affairs. There HAD to be a better way. There was. At the
suggestion of Jim Gaudreault, the resident microprocessor guru, I
decided to write a monitor. Because the product hardware was mostly
developed, the monitor board had to function as an emulator --
plugging into the processor socket of the application hardware.

The primary benefit of the monitor is the ability to patch errors
without repeating the edit, assemble, burn EPROM cycle. Sometimes
these patches alter only a byte or two. More often than not, they
involve a jump to an unused section of RAM where a hand assembled
routine is inserted. I learned to note all changes and patches on
the front page of my listing. The RAM is unprotected and all too
easy to clobber with untested code. Having all the patches noted in
one place and in hexadecimal makes it easy to check the integrity of
the code and to reenter the patches when necessary. When the number
of patches became inconveniently large, usually right after a power
failure or a bad memory crash, I would retreat back to the editor for
a fresh start.

Another handy debugging tool is the breakpoint capability. While the
breakpoint in this monitor is extremely limited and simple, it does
provide a means of verifying which path of a conditional jump was
taken without using the logic analyzer (which was always in use by
someone else). Furthermore, you can check the actual values in
variables and internal registers instead of deducing them from
external behavior.

A third technique I found very helpful was to insert temporary test
patches that displayed the value of some variable each time through a
loop. This was accomplished by calling monitor subroutines via a
convenient jump table provided at the start of the monitor. This
technique was at the expense of real-time operation, but provided
insights into the dynamics of the software that were unobtainable by
any other means.

Some basic utilitarian functions were necessary, too. These allowed
you to initialize memory, copy a block of memory, compare two blocks
of memory and hexdump a block of memory. Of course, commands were
also required to edit the contents of memory and to execute
instructions starting at any arbitrary location. I also added some
convenience commands to display the command syntax and the locations
in the jump table and to perform hexadecimal arithmetic.

I had the luxury of studying the code of a 6800 monitor that Jim had
written. Many of the features are influenced by his prior
experience. Some, however, are the result of my own prejudices, and
some are reflections of the unique architecture of the 8031. These
functions will be examined in more detail after we take a look at the
hardware.

THE PROCESSOR

The Intel 8031 single-chip microcomputer is the romless version of
the 8051. This is the high end of Intel's 8-bit microcontrollers.
It has five interrupts: two external inputs, two hardware timers and
one hardware asynchronous serial port (UART). Even using a 16-bit
external address bus leaves 16 bidirectionsl I/O ports. The
processor has three separate memory spaces: a 64K program memory
space, a 64K external data memory space and 128 bytes of internal
RAM. The 8031 even features a hardware multiply and divide.

While this processor has a lot of speed and power, it has its share
of ughs, too. The architecture is accumulator-based. This means a
lot of processing time and bytes of code are used in loading and
storing the accumulator as compared with a register based
architecture such as the Zilog Z-8. The instruction set is highly
irregular. For example, the only compare instruction is a
compare-and-jump-if-not-equal. To do a compare-and-jump-if-equal
requires an exclusive-or, a subtract, or a jump-around-a-jump. A
jump-less-than or a jump-greater-than also involves extra work. This
can be seen in the ASCII-hex to binary conversion in listing 1. This
is in contrast with the Motorola MC6801 which even has a branch-never
instruction to maintain orthagonality. The instruction set also has
very few instructions that can access those two large external memory
spaces.

THE EMULATOR BOARD HARDWARE

The hardware design of the emulator board is straightforward. Figure
1 shows the schematic diagram. It features 4 Kbytes of monitor
EPROM, 8 Kbytes of application EPROM space, and 8 Kbytes of emulation
RAM. With the current prices for larger memories it would make more
sense to use 8K X 8 monitor and application EPROM chips and an 8K X 8
emulation RAM chip. When this circuit was first designed, however, a
64 Kbyte static RAM cost approximately fifty dollars. Using the
larger memories could save one 74LS138 address decoder chip, not to
mention the additional sockets and wiring.

The separate external program and data memories pose a problem in the
design of the emulator. The monitor and application EPROMS are in
the program memory. That is easy enough. The RAM, however, must be
in the data memory because there are no instructions for writing to
the program memory. On the other hand, the purpose of the RAM is to
allow altering the executed code. Therefore the RAM must be part of
the program memory so that instruction fetches may access it. The
solution, of course, is to map the RAM into both memory spaces. The
signal RAMSEL* is true if PSEN* is true or if RD* is true. Figure 2
shows a memory map of the emulator board.

An attempt was made to use as few of the resources of the processor
as possible. The exception to this is the large amount of external
memory space used. The project that prompted this monitor used very
little external memory. It did use the internal UART. Therefore, an
8251 USART was used for communicating with the terminal rather than
the internal UART of the 8031. Clocks for the 8251 are provided by a
Motorola K1135A dual baud rate generator. This handy device contains
both a crystal oscillator and two divider chains in one 18 pin DIP
package. If this is not available, you may substitute the alternate
circuit, shown in figure 3, using a 74HC4060 oscillator/divider chip.

The address, data and control signals are buffered to allow for the
extra loads placed on them by circuitry on the monitor board. If
these signals are also buffered on the target board, it may cause
excessive propagation delays. If necessary, replace one set of
buffers with jumpers. Jumpers are provided to connect the on-board
crystal or an external clock from the application board. Similarly,
there is a jumper selection enabling the on-board reset button, the
application board reset or both. Another jumper allows connecting or
isolating the grounds between the target and emulator boards.

OPERATION OF THE SOFTWARE

First edit and assemble your source code. Program the object code in
EPROMS and plug them into the application code space. Note that the
code will be executed out of RAM. Therefore, the origin statement in
the source code should specify 4000H, the start of RAM, rather than
2000H, the start of the application EPROM. The interrupt vectors in
the monitor EPROM all jump to the equivalent offset in the RAM space,
allowing use of all the interrupts with only a slight additional
latency. The reset vector, however, jumps to the monitor
initialization code. This scheme allows the application EPROMS,
burned for development purposes, to be used in the final project in
some cases. In the products I developed with the aid of this
monitor, the memory mapping was incomplete. In the program memory,
4000H and 0000H were indistinguishable.

The commands are invoked with just the first character of the command
name. There are a few exceptions to this rule. The internal
varieties of the DUMP and ALTER commands have an 'I' appended to
distinquish them from their external brothers. The HEXMATH command
is invoked with a number sign, '#'. All numeric values are expressed
in hexadecimal. If you mistype an address or a data value, just keep
typing. The last four digits are accepted for address values and the
last two for data values. It isn't necessary to type leading zeros
unless you're covering up a mistake. To cancel a command that you've
started to type, just type any illegal character. To abort a command
that outputs to the screen, merely type any character. These rules
are consistent for all the commands.

THE COMMANDS

The first command needed in any debugging session is the COPY
command. Copy the application code from the EPROM to the RAM. Be
sure not to overwrite the last nine bytes of RAM. These are used by
the breakpoint routine.

Next, use the VERIFY command to make sure the transfer went ok.
VERIFY indicates agreement between two blocks of memory by doing
nothing. Any differences are displayed. I never got around to
adding a memory test. With only four RAM chips it didn't seem worth
the effort. Usually corrupted memory was the result of stack
overflow or some other errant code. Always VERIFY the damages after
your code goes into the weeds.

Run your code using the GO command. If you do not specify an
address, execution will restart at the breakpoint. More about this
later. Although the most likely starting place is 4000H, the reset
vector of the application code, you can GO to any address in the
program memory.

This code took a trick to accomplish. The 8031 does have an indexed
jump instruction, "JMP @A+DPTR". Unfortunately the data pointer is
only easily loaded by a constant -- a variable must be loaded a byte
at a time -- and the accumulator is only eight bits. Besides that, I
wanted to be able to restore the data pointer and the accumulator
when resuming after a breakpoint. The simple solution, shown in
listing 2, is to push the address onto the stack and execute a
return-from-subroutine instruction. This trick has also been used
elsewhere in the code.

Before running your code you may want to set a breakpoint. The BREAK
routine requires a little explanation. Many processors have a single
byte software interrupt instruction that can be used for breaking
back to the monitor. This single byte may be substituted for the
first byte of any instruction. When the software interrupt is
executed, it transfers control to a breakpoint routine. The 8031
lacks such an instruction; we must use a long jump instruction, which
is three bytes long. The break address must be aligned with the
first byte of an instruction so that it will be executed and not
treated as data.

First we want to save the original instructions. Because an 8031
instructon may be one, two or three bytes long, the three byte jump
instruction may clobber a sequence of three, four or five bytes of
code. This is the optional final parameter of the break command.
Three is the most convenient value and is the default. This
parameter is stored at the location BYTENUM and the bytes of code are
stored in the following five bytes (padded with NOP instructions if
necessary). Following this, the final three bytes of RAM are filled
with a jump instruction to the location following the code that was
copied, i.e. "BYTENUM" bytes after the break address. After all this
is done, a jump-to-the-breakpoint-routine instruction is written at
the break address.

When execution reaches the break address, control passes to the
breakpoint routine. The breakpoint routine saves the registers that
are treated as volatile memory and displays the values that they
contained. A GO command without a starting address resumes from the
break address. First it restores the saved registers and executes
the saved code, and then it jumps back to the application code
following the break address. Before entering a new GO command with
an address following a break, you should first reset the board. This
will recover the five bytes of stack space used to store the volatile
registers.

The code saved at location BYTENUM+1 is restored to its original
location when a new break address is entered or if the BREAK command
is invoked without a new address. This should be done before a reset
because the initialization code clears BYTENUM and the following
locations.

The DUMP command comes in two flavors, internal and external. The
external version does a memory dump of the program memory in
hexadecimal showing the ASCII translation to the right. Any starting
and ending address may be given and the columns still line up. See
example.d. If no ending address is specified, 0FFFFH is assumed. A
DUMP may be interrupted, as can any command that writes to the
screen, by typing any key.

The internal variety of the DUMP command is similar but dumps the
internal RAM and the special function registers. No ASCII
translation is shown. It's unlikely to find ASCII strings in
internal memory. The special function registers are indicated
symbolically in addition to their addresses. This is shown in
example.di.

The ALTER command also comes in two flavors. The external variety
displays the current byte from program memory followed by a dash,
'-'. If you enter a hexadecimal value, that value will be inserted
at that location in external data memory. A space or a carriage
return leaves the location unchanged and displays the next byte; the
difference between the two being that a carriage return puts you on a
new line. A period or a backspace backs up the displayed byte by one
location. Any other non-hexadecimal character cancels the command.
Remember that the RAM on the monitor board appears in both the
program memory and the data memory. The ALTER command writes to data
memory because there is no way to write to program memory. Reads are
made from program memory. It is frequently convenient to use the
ALTER command to check code, hitting carriage return after each
instruction for readability.


The internal form of the ALTER command behaves similarly, but with
the internal RAM. Again, like with the DUMP command, when you reach
the special function registers, locations above 7FH, the name of the
register is displayed. Both the DUMP and ALTER commands use the SFR
TABLE to access these registers. The special function registers
cannot be accessed indirectly and the memory space they occupy is
sparsely populated. The SFR TABLE provides a solution to both of
these problems.

Each table entry is eleven bytes long. The first five bytes give the
symbolic name of the register. At an offset of five is a subroutine
to read the register. Within this subroutine, at an offset of six,
is the hexadecimal address of the register. Eight bytes from the
start of each entry is a subroutine to alter the contents of the
register. Some registers, such as the stack pointer, may not be
altered because that would crash the monitor. These entries return
an error flag instead. Some registers, such as the accumulator, are
treated as volatile by the monitor. They are allowed to be altered,
but no attempt is made to preserve the contents of them.

The MODIFY command allows entering information into external data
memory as characters instead of hexadecimal values. There are only
two special characters to remember within this command. A backspace
backs up the address pointer to allow the correction of mistakes. An
EOT (control-D) terminates the command. There is no internal version
of the MODIFY command. It seems unlikely that the limited internal
RAM would be used for storing strings.

The INSERT command inserts a single hexadecimal byte into a block of
external RAM locations. Incidentally, this command is called INSERT
rather than FILL because I always intended to add a FIND command.

The final three commands, HELP, JUMPTABLE and HEXMATH, are
conveniences. HELP displays the syntax of the commands and JUMPTABLE
displays the entry points to the utility subroutines. HEXMATH,
invoked by '#', performs both addition and subtraction in
hexadecimal. This is very convenient for calculating relative jumps.

Some people might consider these last commands to be insignificant,
but I disagree. It takes a very short time indeed to forget the
command syntax. The HELP command also makes this tool easily
available to others. The JUMPTABLE command allowed me to create test
patches much more frequently. Before adding it, I was constantly
looking for the monitor program listing to look up the jump table
addresses. The HEXMATH is a cheap convenience -- convenient to
include when compared with the trouble caused by missing a jump
target by one byte. The point of the matter is that even simple
software such as this should attempt to be as helpful as possible.
There are so many better ways to spend time than struggling with your
tools.

CONCLUSION

This article has focused on one particular implementation of a debug
monitor. If you are using a different processor it is well worth the
effort to create your own. In addition to the debugging help,
writing a monitor is a good exercise. It helps you become familiar
with a new instruction set.

Build your monitor one function at a time. First start with
displaying a signon message. Then accept input and echo it back to
the screen. Once you have the terminal interface working, add the
more basic commands such as DUMP and ALTER. At this point you will
have tools to aid you in developing the rest of the code.

It took me two weeks to develop the hardware and enough of the
software that I was ready to start using my new tool to develop
application code. A year later I was still adding features and
refining functions. Every minute I spent working on the monitor was
quickly repaid in time saved. In the first week I used it, I
accomplished six weeks of debugging measured by my previous standards.

What features would I like to add to this or any other monitor? A
LOAD command to download a hexfile directly to RAM would be first
choice. A FIND or SEARCH command to pick out variable length byte
sequences would be nice. A disassembler and single-line assembler
would be a great help. These two are big jobs, though. I got to be
pretty good at patching code with hand assembly, but I was doing it
every day. The list could go on and on. A monitor is never finished
until you quit using it.


 December 5, 2017  Add comments

Leave a Reply