==================================================================== 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.