textfiles/programming/desdebug.txt

479 lines
25 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

====================================================================
DR 6502 AER 201S Engineering Design 6502 Execution Simulator
====================================================================
Supplementary Notes By: M.J.Malone
Suggested Programming Style and Debugging Methods Using DR6502
==============================================================
So far in the documentation, you have been introduced to the
6502, its assembly language, the project board and the 6502
simulator. Several times there have been references to 'proper' or
'suggested' programming style. These suggestions are for the
purpose of making debugging easier. Learning and writing assembly
language is not difficult since the commands are limited to a finite
set each performing very simple manipulations. Debugging assembly
language is THE hardest problem. Even with a software simulator
with extensive program monitoring, finding bugs in a code is very
difficult. By following certain programming methods, bugs are more
easily located.
'Bottom Up' vs 'Top Down'
-------------------------
Much discussion has been spent on the 'Bottom up' and 'Top
down' programming styles; each has its merits for different
applications. The 'Top down' strategy involves writing the highest
logic level of the program first and then the subroutines and then
the subroutines of subroutines etc until the task is complete. The
'Bottom up' strategy involves writing basic functions first and then
building on them to produce a the code from the bottom of the
pyramid.
The 'Top down' method has advantages especially if several
people are working on the same code. The main logic could be
decided and the task divided into modules which could be distributed
among the team members. The data structure and the calling
parameters for each module would have to be fixed to assure
compatibility. Problems can arise if the early definition of the
problem and its division into modules leads to some unanticipated
difficulties at the level of the elementary subroutines. For
example, duplication of tasks can occur when two modules are
designed that require (because of their definition) slightly
different subroutines that perform essentially the same function.
As a result of the duplication, the program is longer and has more
parts to debug. This is not a problem since there are more people
to debug the program and computer memories are usually not a
limiting factor. The entire task, with a team at work, when
programming 'Top down' can be completed quickly.
The 'Bottom up' method has advantages especially when starting
at machine level applications. The basic capabilities required to
manipulate data are identified and are written. From there the
program develops as ever more complex manipulations until the main
program can be written to perform the required task. Problems can
occur in 'Bottom up' style if a clear idea of the goal of the
program is not kept in mind. For example when advancing to a higher
page 2
level manipulation it may be realised that a poorly defined storage
method for some critical data element may have to be changed.
Reprogramming would have to begin at the lowest level of the
occurrence of the problem. 'Reaching the top' may seem impossible
when working on the manipulation of basic items. A 'Bottom up'
strategy does not duplicate low level functions and will lead to
smaller and probably faster code. 'Bottom up' strategy is not
easily allocated to a team and is more difficult to develop in
parallel. 'Bottom up' strategy allows early debugging of basic
functions before they are obscured by higher level manipulations.
There is a history of 'Bottom up' programming in computer
control applications especially with the FORTH computer language.
FORTH is a bottom up language where you MUST enter basic functions
first and these are compiled as entered. When higher level
functions are entered, their calls to the lower level functions are
converted to pointers to the already compiled block of code. As the
code is developed the higher level functions are appended to the end
of the code until the main program module is entered. The code
executes very fast like a compiled language but is entered as an
interpreted language. There are in fact even FORTH processors that
operate in the FORTH language instead of assembly language and
execute computation and control programs very quickly. At the FORTH
prompt, the user can type a function call with its arguments and
test the operation of that function.
Recommended Methods
-------------------
It is recommended that students use a 'Top Down' strategy when
planning the data structures and general program flow in the form of
a block diagram. From this diagram, the range of basic functions
required can be listed. The student should then set about
programming the software in a bottom up fashion, testing each
subroutine as it is coded. Strong use of subroutines should be made
to reduce the length of the code. The reason for this is not that
it is likely that a student will run out of memory with a maximum of
16K of program space on a project board. The reason is, the shorter
a program is, the less there is to debug. The more a subroutine is
used, the more is gained by proving it correct. Once the core of
basic subroutines are proven, the student can advance to the next
tier of nesting with confidence in what has been done thus far.
The first routines programmed will likely be the basic
multibyte mathematics subroutines. Since values sometimes cannot be
expressing in one byte and often these quantities must be added or
subtracted, there are few software subsystems that will not require
some math routines. Other routines that are likely to be required
are a delay loop of a controlled length and basic I/O interface
routines.
After each routine has been written, the student should use
DR6502 in the software mode to test the response of the routine to
inputs. For example if the routine adds, then make it add several
page 3
numbers and check to see if its answers are correct. Check I/O
routines in software mode first and then if the actual interface
circuitry is ready and tested, the integration of software and
interface should be attempted either with a test EPROM or using the
simulator in hardware mode.
Often students will give their code to DR6502 without providing
any input data. DR6502 executes the instructions, the screen flips
and the program reaches its end. The mistaken conclusion is that
the routine or program works. The simulator does not check to see
if the algorithm of the program conforms to any proper model
intended by the student. The simulator does not check to see if
data was processed properly. The only error conditions that the
simulator will flag and actually stop the execution over fall into
the category of dumb mistakes. If a program executes and DR6502
does not complain it just means there are no writes to EPROM, no
reads from a location that was never set and no reads or writes from
unallocated memory spaces. It is very important therefore to
provide input data, check intermediate values and check responses
and output values before any conclusions can be reached about a
particular piece of code.
After and only after the basic routines are tested, should the
higher level routines be tested. Because higher level routines may
not test as wide a range of input values to the lower level routines
under simulated conditions, it is never wise to test several levels
of the program at once. This is the most common problem for
students. Many students will very often write the entire program
and then test it as a whole, hoping it will all work. Often the
student will not input as great range of inputs into the program as
would be in the actual prototype and the tests will appear to
succeed. Since such full tests are difficult and time consuming to
stage without the hardware present there will be little motivation
to go on and do more. Toward the end of the term, students are
sometimes afraid to test their software more extensively in case
they do find a terrible bug that would take longer to fix then the
time available. Unfortunately, in the final design and testing
these undiscovered or ignored bugs will crop up leading to some
tough questions from instructors and group members. Make sure that
all tests are carefully controlled, testing the performance of one
routine. Any routines called in the testing of the routine under
investigation are thoroughly tested before hand.
Often students will test a routine and go on to test another.
When problems are found in the second routine and the reason is not
apparent, the student may begin to doubt the performance of the
first routine. At this point it becomes important for there to have
been notes on the tests performed on each routine. By consulting
these notes, the student will be sure whether the routine is
functioning or if some aspect of it being used that was in fact
never tested. To help in this matter, the DR6502 program outputs a
history file providing a record of all work done during a simulator
session. The log file is called DR_6502.HST. Though notes in a lab
book are still necessary, after a simulator session, the student may
wish to rename the history file to a name indicative of the test
performed and copy it onto a floppy or into a a different directory
page 4
where it can be consulted if necessary. In discussions with
instructors or group members, these records are good sources of
information on what problems were encountered and solved and how
they were solved.
In summary, the program should be planned from the highest
logic downward and then coded from the lowest level upward. Well
designed tests should be performed on each routine as it is
completed. Tests should provide a wide range of input values to the
routine, monitor intermediate values responses and outputs. It is
wise to keep extensive records of the tests performed.
Using DR 6502 to Test Software
------------------------------
DR 6502 is a tool that is versatile but has no options like
'test everything and see if it works.' To prepare for the software
testing stage of the design, the user of DR 6502 should read the
DR6502.DOC file and try hacking up and running the example program.
Performing a test on the software will also require a firm idea of
what the goals of the test are to be. Once this has been
established, the steps in a test should be:
1) Prepare the subroutine or segment of the program to be tested
(including the reset vector and a start up routine initializing the
stack), assemble with TASM and create a symbol file with DRSYM.EXE.
2) Start the simulator, provide all requested information and
proceed to the 'stopped' status screen.
3) Prepare any input data required for the program's execution.
4) Set any output options that are necessary to monitor the progress
of the program.
5) Begin execution of the program, monitoring its progress.
6) Upon completion of the test, examine the output data and save it
to disk if necessary.
7) Modify the program and repeat or exit the simulator and copy the
history file to your records disk.
In the case of testing a 2 byte addition subroutine the user
may do the following. As above, the program is prepared, assembled,
the symbol file is created and the simulator is started. The
specification of this routine includes that, as input variables, it
takes one 16 bit number passed into the subroutine call by the
values of the .X and .Y registers representing the high and low
bytes respectively. The other 16 bit number is present in memory
locations $00 and $01 as low byte, high byte respectively. The
routine is designed for doing a 'running total' type addition where
the answer is stored back into the memory location $00 and $01
before the routine executes an RTS. The output variables of this
subroutine are a copy of the contents of $00 and $01 in the .Y and
page 5
.X registers respectively, the answer. Also returned by this
routine is the value of the carry flag: set if there was an
overflow, clear otherwise. The overflow flag was not used because
the programmer is using it as a quick input port (with the SO pin of
the processor) in another routine.
The user selects the 'p' option to poke initial values into
the $00 and $01 memory locations, and inputs '$00 00' to set both
locations to zero. The user then selects the 'r' option to modify
the .X and .Y registers and sets each to zero as well. The first
test will be to see if the routine can add zero and zero and get
zero. This is essentially testing to see if any obvious errors
exist in the way the routine handles the carry flag. The user then
selects the output options: 'H' to select full history mode, 'm' to
monitor the address $00 as a two byte variable and 'b' to set a
break point after the addition of the least significant bytes of the
arguments. The user then executes the subroutine by pressing the
's' key for each program step or 'g' for continuous execution. The
user carefully observes the screen outputs and when the RTS is
reached if the code found 0+0=0 then another test would be
prepared. When the testing of this routine is complete the user
exits the simulator and enters the DOS command: 'copy dr_6502.hst
a:\tests\2byteadd.rec' to copy the history file into the test
records directory.
Testing I/O Routines
--------------------
In the above example, the subroutine processes input data and
produces output data all internal to the 6502 address space. In
such cases DR 6502 is sufficient, in its software form, for complete
debugging of routines. In the case of I/O subroutines, there is
information that originates outside the 6502 address space involved
in the manipulation. The problem can be divided into to classes of
subroutines: those that include hardware input and those that do
not.
If the subroutine is an output subroutine only, with monitoring
of the appropriate output port address, a software simulation with
DR6502 would be sufficient to check the algorithm. To check to see
if the routine would 'work' in the global sense, IE be sufficient to
drive the external device in the manner required, then the project
hardware must be brought into the loop, either through the hardware
simulator or by burning an EPROM and trying it on the target system.
If the subroutine contains hardware input then the simulation
becomes more difficult. Naturally, working in hardware simulation
mode, with the target system present or with an EPROM on the
completed target would test input subroutines. If the user would
like to test the routine with the simulator in software mode, the
task is more complex. The user must use the 'b' break point option
to halt the simulation and do inputs manually. The user may halt
the execution before the input read and put the data in the input
port with the 'p' poke memory location option. The user may wish to
use the 'b' break point option and set it to the port location.
When the simulator executes and reads the port, the simulation would
stop. The user could then put the NEXT value on the port. This
method is superior especially when the subroutine next reads the
port at several points in the code depending on the last value read
in.
page 6
Hardware testing is essential to development of the software.
For example the control of a stepper motor requires a certain series
of bits or combinations to be sent to the output port. The
programmer may easily program these combinations and make sure they
are sent to the port in the correct order but the system may still
not work as a whole. The programmer may have neglected or chosen
the time constants, in this case the waiting periods before sending
the next bit code, incorrectly. A few tests with a function
generator and a few logic circuits may give the programmer an idea
of the time delays between sending each impulse. Unfortunately,
using a function generator is not always fool-proof either. The
maximum frequency, as read on the generator, that the motor will run
at and the frequency it will start at are two different values. An
acceleration routine would be required that starts the motor with
time constants corresponding to the lower frequency and slowly
shifts them to the values required for the higher frequency. The
motor would then appear to start and them accelerate.
Unfortunately, this is often not adequate either. Once the motor
has been mounted in its final configuration and attached to the
final mechanism with all the masses required to be moving, the
torque required from the motor and the momentum changes these
constants. Usually the motor must be run more slowly however in the
case of a high angular momentum and a small running torque like a
flywheel, the maximum frequency of the motor may actually increase.
Factors like mass and inertia that lead to time constants. Friction
alters those time constants and is found in all practical systems.
Such factors must be considered in the design of software and only
testing on the physical hardware can prove that the software is
adequate.
Unfortunately, the hardware simulator does execute instructions
as quickly as an actual 6502. As a result, the hardware simulator
is not well suited to determining time constants. Testing on the
actually hardware with a test EPROM would be required in this case.
For additional suggestions on how to determine time constants with a
test EPROM see file 'TRICKS65.DOC' Section 3) 'The Reburnable EPROM'
particularly the 'Vector Jump Method'.
More Advanced Testing
---------------------
After the basic subroutines have been tested, the student will
then move on to testing the more advanced routines that call the
lower level subroutines. After a while the purpose of the routines
moves away from the basic manipulation of data and on to the higher
level logic of which routines are called in what order to perform a
task. There are user selectable modes of operation where the
simulator does not produce as much extraneous output. The methods
involve the output to the screen and the history file records.
The history file can be set in one of three modes of output to
limit the quantity of unnecessary output not of interest to the
user. The total output mode, outputs instructions, arguments,
register values and monitored address values. When the flow of the
code is important but the register values are superfluous
information, there is a second mode (default) where the history file
can be made to contain only the instructions and arguments. In
page 7
addition, if only the call structure of the program is important
then the history file can be set to more brief output mode where
only program steps involving the JSR, RTS, IRQ, NMI, BRK and RTI
instructions are recorded.
Consider the following call tree typical of a mobile reactive
robotics system:
Main
|
-------------------------------------
/ | \
Sense Record React
| | |
|
|
----------------------------
/ \
Compute Execute
| |
|
|
-----------------------------------------------------------
/ | | | | \
Add Subtract Multiply Divide Square Root Arctan
Let it be assumed that the code has been designed from the top
down and is being programmed and tested from the bottom up (though
this is not necessary). After the basic math routines have been
programmed and tested and the 'Compute' algorithm has been written,
the user would like to next test the 'Compute' algorithm. When the
simulator executes, it would normally display all the program steps
(full output mode), none of the steps (silent mode) or none of the
steps up until a break point. If the user is confident of the math
routines and is really only interested in the behaviour of the
'Compute' subroutine then a mode that limits the screen output based
on the position of the execution in the call tree is required. Just
such a mode is available in the simulator as the subroutine trace
option. In the above example the user would execute the trace
option at any time that execution was in the 'Compute' subroutine.
From that point on, none of the steps of the math routines would be
displayed nor would any steps of subroutines called by 'Execute' be
displayed. If the entire program were present and execution
continued to other areas of the calling tree, all subroutines at or
above the level of 'Compute' would be displayed. Since limiting
screen output also limits history file output, the two methods above
can be used in combination. When this option is selected, only
program steps at the 'traced' level of nesting or higher are
displayed and hence are eligible to be recorded in the history file
depending on its output mode. The user may chose at any time to end
the trace option.
page 8
Recommendations for Testing
---------------------------
When testing the subroutines of the code, make sure the purpose
of the test is clearly laid out, the progress of the execution well
monitored and the results of the test recorded in appropriate
detail. If the simulator is used, use all options available to
monitor and record the progress of the code. If the code is run on
an EPROM, make sure that adequate output is given to whatever output
device is available to follow the execution of the program. Use LCD
displays whenever possible, connect LEDs to any unused output bits
and program the code to give signals to indicate the course of
important decisions.