home   |   stacks treatise index   |   1. Intro: stack basics   |   2. subroutine return addresses & nesting   |   3. interrupts   |   4. virtual stacks   |   5. stack addressing   |   6. passing parameters   |   7. inlined data   |   8. RPN operations   |   9. RPN efficiency   |   10. 65c02 added instructions   |   11. synth instructions w/ RTS/RTI/JSR   |   12. where-am-I routines   |   13. synthesizing 65816 stack instructions   |   14. local variables, environments   |   15. recursion   |   16. enough stack space?   |   17. forming program structures   |   18. stack potpourri   |   19. further reading   |   A: StackOps.ASM   |   B: 816StackOps.ASM   |   Appendix C


6502 STACKS TREATISE


Appendix C:  RPN calculators, a test application, assembly language, and Forth

Although related and relevant to stacks, this gets a bit off-topic for 6502 assembly language, so it's an appendix.  It's a portion of an email I wrote to a smart nephew who was thinking about what direction he wanted to go in programming and robotics as he started college.


Many programmable calculators use keystroke programming which means there's a single operation per program line, instead of many operations on a line.  For example, as you key in a program and need the cos function, you press the cos key and a line is added with that single instruction.  Here's an example, a portion from an actual test program we used where my HP-41cx calculator controlled the automated testing of headsets.  I added comments beyond the normal so you can better understand how a keystroke-programmed calculator can control external instruments.  I hope your monitor doesn't wrap the lines (here or in any of the other code).



 90  LBL 04        \ Numeric label.  Alphanumeric labels are allowed too, but they're less efficient in this case.
 91  FC? 10        \ If it was testing the right earphone before (indicated by Flag 10 being Clear),
 92  "0046"        \ then connect the left instead.  This string of digits (as text) is a command to the Cytec relay box.
 93  "├01471145 "  \ Add to string: Disconnect +13V from DMM line, and connect microphone DC voltage line to DMM line.
 94  7
 95  SELECT        \ Now select the Cytec relay unit to talk to (whose address is #7 on the HPIB bus).  It has 128 relays.
 96  OUTA          \ OUTput the Alpha register (now containing the text string of digits formed above) to it.
 97  22
 98  SELECT        \ Now select the DMM (digital multimeter) to talk to (whose address is #22 on the bus),
 99  IND           \ and INput a Decimal number from it, which from previous set-up, makes it take a voltage reading and send it.
100  STO 17        \ STOre it in register 17.
101  FIX 02        \ Put the display mode in FIXed-point, showing two decimal places.
102  VIEW X        \ Put the resulting reading in the display.  It is also stored and will be printed later if operator chooses.
103  "01451142"    \ Disconnect the mic DCV line from the DMM and connect the mic plug tip voltage (for push-to-talk test).
104  7             \                                                  (The string of digits is a command to the relay box.)
105  SELECT        \ As above, select the Cytec relay box to talk to.
106  OUTA          \ Like line 96.  The string in line 103 gets sent to the relay box.
107  22
108  SELECT        \ Select the DMM again.
109  IND           \ Like line 99.  Get mic plug tip voltage to check for shorts to center ring or ground.
110  STO 14        \ STOre the voltage in register 14.
111  9
112  X<Y?          \ Is 9V less than the tip voltage?  (ie, is the tip voltage over 9V?)
113  GTO 05        \ If so, skip the problem message and go down to label 05.  Normally it's at the voltage of the 13V power supply.
114  SF 08         \ Set Flag 08, signaling a problem.
115  FS? 09        \ Is Flag 09 Set?  A "yes" mean it's in printout mode and should not stop with each problem but rather continue,
116  GTO 05        \ and skip the problem message in the display.  (Resume down at label 05, and print all test results at the end.)
   <snip>
137  22
138  SELECT        \ Get ready to talk to the DMM again.  (22 is the bus address of the DMM.)
139  "ACV;R,5,.02" \ Tell it to get ready to measure AC voltage with a maximum of 5V input and give .02% (1mV) precision
140  OUTA          \                                                         (This sets the range and number of digits.)
   <snip>
150  9
151  SELECT        \ Get ready to talk to the signal generator (which is at address 9 on the bus).
152  CLRDEV        \ CLeaR DEVice (like a software reset).
153  INSTAT        \ INput STATus to clear any error conditions that may exist.
154  "A.05P1I"     \ Set Amplitude of signal generator to .05V (50mV) and enable output.  The "I" makes it go ahead and execute
155  OUTA          \                                    the command string.  (Frequencies and amplitude adjustments come later.)


The program was 20 pages long and accessed a half-dozen files.  Even from this little bit, you can see the workings of RPN keystroke programming controlling a rack of equipment.  Each piece of equipment also has its own command language like you see in the quoted strings above.  When people saw this in the 1980's, controlled by something that would fit in your pocket, they nearly needed a change of clothes!  :D  As alluded here, the HP-41 does not allow variables, flags, and constants to have names, although labels can, and programs and files always do.  In most languages, you'll usually use names for variables, arrays, flags, constants, etc..  The equivalent above would be to say for example DMM SELECT instead of 22 SELECT, where DMM would return the address of the DMM (digital multimeter); also something like TIP_VOLTAGE (a variable's name) would be used in place of 14 which is the register number I chose to store it in.

I suppose it's called "keystroke programming" because in the early days of programmable calculators, before they had way too many functions to give them all their own key (like my HP-41 with potentially thousands of functions), you'd just press the key (for example SIN, COS, LOG, etc.) giving the desired function when you're in program mode and it would be put in the program as a single step, without having to spell things out like you have to in other programming environments.

My very first programmable calculator (a TI-58c) did not have an alphanumeric display; so the display would show the program step number and just a number representing the function by the row and column of its key, making it kind of like the machine language corresponding to the keystroke programming being like the assembly language.  (I don't know anything about its processor, or its true assembly and machine languages, but you can see the analogy I'm making.)

Calculator keystroke programming seems to bring quite a benefit later for grasping assembly language.  Today, the grasp of assembly seems to be getting lost, and the common mentality is that the way around it is to just throw more processing power at the problem because it is getting cheaper and more available to waste.

So why would we still use assembly for anything?  Suppose you need events on output pins to be .000001 seconds (1µs), ±5%, apart.  (This would be irrelevant to the kind of programming <our daughter-in-law> does, but not to technical programming for controlling circuitry on the workbench, possibly including robotics.)  If the processor is fast enough and a compiler for it produces efficient code, you may have the speed to do it; but you will not have control of the exact amount of time taken.  For that, you need to use assembly.  You won't have that kind of control in a higher-level language.  (That's not to say the entire application needs to be written in assembly.  Often it only takes a little assembly to meet the requirements, and the rest can be in a higher-level language.)

Some of the highest-performing microprocessors however have assembly languages that are totally impractical for a casual user to learn well; so people who can dedicate a large amount of time to understanding it write compilers so their customers (who are programmers) can use a common language like C and not bother with assembly.  It seems to partly defeat the purpose, since assembly is the way to get maximum performance, yet to get maximum performance they have designed a processor you almost can't program in assembly!  Still, good compilers can bring out some pretty good performance.  (That's not to say all compilers do.  For some processors, I've read of performance ratios of 5:1 or more from the best compiler to the worst, all for the same processor!)

Major arguments against assembly language are that you cannot do structured programming (which is not true at all), and the number of lines of code needed to do things.  People making these arguments however are ignoring how much you can do with assembly macros.  A macro is like a subroutine that the assembler executes in order to assemble portions of code exactly the way you defined in the macro, taking in the various parameters you specify when you call the macro in your source code.  Macros offer a ton of flexibility—far more than initially meets the eye—and leave you in full control of the internal details without making you write them out or look them up every time you need them.  They allow you to effectively raise the level of the language a lot, without losing any of the benefits of assembly.  I have an article on my website about them, at http://wilsonminesco.com/StructureMacros/ which will interest you later.  (Another criticism of assembly is the lack of portability.  That is indeed a valid point; but note that when you use assembly to get the best performance from a given processor, the same processor can be used for a wider array of jobs, so the need to upgrade for the next one is reduced.)

[There's more on the relevance of assembly language today in my 6502-oriented article, "Assembly Language:  Still Relevant Today."]

When I was starting out, I thought I wanted to learn every language out there that was related to my field (meaning I didn't care about ones like COBOL which is mostly for business applications, not science & engineering).  I collected a half-dozen languages (which is a drop in the bucket compared to how many there are), but after I found Forth in about 1990, I didn't want any more.  I was hooked, and I use it on the workbench computer for all kinds of things.  Actually, it was originally developed primarily for robotics-type applications.  Although a lot of spacecraft and some large commercial operations are programmed in Forth, Forth will never be very popular in industry, partly because it gives the programmer such freedom and flexibility that a supplier can't say, "You need to buy my new update because it adds this and that and the other capability," because the programmer will just say, "Good idea!" and go implement it himself and not buy the update; because Forth allows you to "get under the hood" and modify and extend even the compiler itself.  Want a new operator?  A new kind of structure?  A very different kind of function?  Make it.  It's no problem, and your creation actually becomes part of the language.  I don't know of any other language that allows that.  It had the tools to do object-oriented programming (OOP) even before it was even called that.  It kind of makes life difficult for a salesman.  (Another interesting effect is that it makes good programmers better, and bad ones worse.  So if you hear someone say it's an unreadable language, blame the programmer, not the language.)  Managers might also feel less in control when the programmer has so much freedom.  I will probably always work in tiny companies in niche markets though where I'm the whole department (or close to it), so I can do things however I want!  :D :D  I can still mix in some assembly whenever I need maximum performance and the tightest control of timing though.

Here's a teensy taste of what Forth looks like, heavily commented for non-Forth-speaking people.  This routine called 232TYPE is for outputting a string of characters (like to print it) on the RS-232 device.



: 232TYPE    ( addr len -- )   \ Start with the address and length of the string on the data stack, and return nothing at the end.
   BOUNDS                      \ Turn address and length of string into beginning address and ending addresses.
   ?DO                         \ Do this loop once per character.  If beginning and end address match, length is 0, so skip the loop.
      I C@ 232EMIT             \ Get loop counter (the address of next character), fetch character at that address, & call "232EMIT".
      232ERR  @                \ Read variable 232ERR to see if 232EMIT flagged any error conditions.  (0 means no errors.)
      IF  CONSOLE LEAVE  THEN  \ If it did, put the output device back to console (primary display), and leave the loop.
   LOOP       ;


There is no punctuation in Forth, and almost no syntax requirements.  (Even the things that look like punctuation are actually words with very short names.)  There are no piles of parentheses.  Parentheses are for comments, particularly where you might want to continue compiling after the comment, on the same line.



last updated Nov 7, 2017