home *** CD-ROM | disk | FTP | other *** search
/ ftp.barnyard.co.uk / 2015.02.ftp.barnyard.co.uk.tar / ftp.barnyard.co.uk / cpm / walnut-creek-CDROM / JSAGE / ZSUS / PROGPACK / MEYERTUT.LBR / MEYER05.TZT / MEYER05.TXT
Text File  |  2000-06-30  |  12KB  |  287 lines

  1.              CP/M Assembly Language
  2.             Part V: The Stack
  3.               by Eric Meyer
  4.  
  5.      It's time to confront the big remaining mystery of assembly
  6. language programming: the stack. Actually we've already been
  7. using it clandestinely, every time we did a CALL; but there's
  8. much more to it than that.
  9.  
  10.  
  11. 1. The PC and SP Registers
  12.      There are two more (16-bit) registers in the 8080 CPU that
  13. we have not mentioned before. Their purposes are very specific,
  14. and you seldom manipulate them directly:
  15.  
  16. +------------------+
  17. |     SP       |      "stack pointer"
  18. +------------------+
  19. |     PC       |      "program counter"
  20. +------------------+
  21.  
  22.      The PC is simple to understand: it contains the address of
  23. the next instruction to be fetched and executed. Thus, for
  24. example, a JMP command is actually loading a new value into the
  25. PC, causing instructions somewhere else to begin executing.
  26. Otherwise, the 8080 simply increments the PC to fetch the next
  27. instruction.
  28.      The stack pointer is the address of a (relatively small)
  29. block of memory that can be used for temporary storage. The
  30. "stack" functions like one of those spring-loaded piles of plates
  31. at a cafeteria: when you put a plate on, the rest move down; when
  32. you take one off, the rest move up. The last item put on the
  33. stack will be the first to be removed ("last in, first out"), and
  34. everything works fine as long as the stack doesn't fill up, or
  35. overflow.
  36.      When a value needs to be stored, the SP register is
  37. decremented, and the value is saved in memory at that address.
  38. When it needs to be retrieved, it is recovered from memory, and
  39. the SP is incremented to its former value again. Thus the SP
  40. points to the "top" of the stack. (Although the stack is actually
  41. upside-down, growing downwards from higher addresses to lower
  42. ones, for various arcane reasons.)
  43.      Consider what happens when you do a CALL.
  44. The 8080 has to remember where it was, so it can RETurn. It takes
  45. the value in the PC, which is the address of the next
  46. instruction, decrements SP, and stores the address there. Then it
  47. loads the new address (of the subroutine) into the PC. When the
  48. subroutine's RET is encountered, the 8080 fetches the previous
  49. address back from the stack into the PC, and increments the SP
  50. again.
  51.  
  52.  
  53. 2. Manipulating PC and SP
  54.      You frequently use instructions -- CALL and JMP -- that
  55. directly change the value of the PC, although you weren't aware
  56. of it. There is also an instruction PCHL, which loads whatever is
  57. in the H-L register pair into the PC. Thus,
  58.  
  59.     LXI    H,xxxx
  60.     PCHL
  61.  
  62. is just the same as JMP xxxx. However, it's nice to know that you
  63. can, e.g., do some arithmetic in the H-L register to calculate an
  64. address, and then jump to it.
  65.      Directly changing the SP is much less common. When a program
  66. runs, CP/M already has set up a modest stack for it somewhere in
  67. high memory, and put a return address in the CCP (console command
  68. processor) on the stack. So as long as you aren't using too much
  69. memory, you can merrily CALL this and that in your program, and
  70. then RETurn at the end, and you're right back to the CCP (A>
  71. prompt).
  72.      This is what we'll be doing throughout this series of
  73. articles. But just for reference, you should know that there are
  74. a number of instructions to manipulate the SP register, in case
  75. you want to play fancy tricks, or to set up your own larger stack
  76. area. Many of the 16-bit instructions you already know can be
  77. applied to the SP, including "LXI SP,xxxx", "INX SP", "DCX SP",
  78. "DAD SP", and "SPHL" (like PCHL). Often you will see larger
  79. programs doing something like this.
  80.  
  81.  
  82. 3. PUSH and POP
  83.      While CALL automatically uses the stack to store a return
  84. address, you also can decide to store other values temporarily on
  85. the stack. The traditional terminology for putting something on
  86. the stack is "push"; similarly you "pop" things off the stack.
  87. Thus there are two instructions, PUSH and POP. They are used
  88. frequently, because there is only a limited number of registers
  89. in the CPU, and as you will find, they quickly fill up. Also,
  90. when you call a subroutine, you may not be sure which registers
  91. it's going to change, and which (if any) will be unchanged when
  92. it returns.
  93.      PUSH and POP work with 16-bit words. Thus: PUSH B pushes the
  94. B-C pair onto the stack; similarly for PUSH D and PUSH H. You
  95. also can PUSH the A-F pair, which for historical reasons is done
  96. by PUSH PSW (for Program Status Word). This preserves both the
  97. Accumulator and all the Flags.
  98.      As an example, suppose you want to send two "?"s to the
  99. console. If you try
  100.  
  101.     MVI    E,'?'
  102.     MVI    C,2    ; Character out function
  103.     CALL    0005H
  104.     CALL    0005H
  105.  
  106. you will likely fail for two reasons. The BDOS (like any
  107. subroutine call) may or may not preserve the existing contents of
  108. registers. Chances are it won't.
  109.      So the second time you do the CALL, the E register will very
  110. likely have been changed, which would give you a different
  111. character. (It's also likely that the C register will have
  112. changed, in which case you'll get an entirely different BDOS
  113. function, which could be quite unpleasant.)  What you need to do
  114. is, of course, the following:
  115.  
  116.     MVI    E,'?'
  117.     MVI    C,2
  118.     PUSH    B    ; Save the BDOS number
  119.     PUSH    D    ; And the character
  120.     CALL    0005H    ; Send it once
  121.     POP    D    ; Restore everything
  122.     POP    B
  123.     CALL    0005H    ; Send it again
  124.  
  125.      Note that if you want things to wind up in the same
  126. registers they were in originally, you have to POP them in the
  127. reverse order to how they were PUSHed ("last in, first out"). And
  128. you always need to balance every PUSH with a POP. Leaving too
  129. much or too little on the stack is the number one cause of
  130. crashing programs. (Remember RET expects to find the return
  131. address on the top of the stack?  If some other value is there
  132. instead . . . )
  133.  
  134.  
  135. 4. And POP and PUSH
  136.      Nobody says you have to PUSH before you can POP. Consider
  137. the following very common, but subtle subroutine, which allows
  138. you to print a message to the console. Unlike other methods, the
  139. message is placed conveniently right into the code, rather than
  140. being off in a data area somewhere, with an address of its own.
  141. You call it just like this:
  142.  
  143.     CALL    SPMSG
  144.     DB    'This is the message',0
  145.  
  146.      Here is what the code for the SPMSG subroutine looks like:
  147.  
  148. SPMSG:    POP    H    ; Get message address into H-L
  149.     SUB    A    ; Zero the accumulator
  150.     ADD    M    ; Get a chtr (Z set if zero)
  151.     INX    H    ; Point to next byte
  152.     PUSH    H    ; Put address back on stack
  153.     RZ        ; Done if at end of string
  154.     MVI    C,2    ; BDOS character output function
  155.     MOV    E,A    ; Hhave to put the char into E
  156.     CALL    0005H    ; Ask BDOS to do it
  157.     JMP    SPMSG    ; Go back for next
  158.  
  159.      And here is why it works. The address of the next byte is
  160. pushed onto the stack to RETurn to when you CALL SPMSG. That
  161. happens to be the beginning of the string. So POP H brings that
  162. into H-L, pointing to the byte to fetch. We use SUB A, ADD M to
  163. get it, instead of the more simple MOV A,M, because this will set
  164. the Z flag when we reach the byte "0", which marks the end of the
  165. string.
  166.      Each time we INX H to point ahead to the next byte, then
  167. PUSH H to put the address back on the stack again. Now, if the
  168. byte just fetched was not the "0", we can use BDOS function 2 to
  169. send it to the screen, and loop back for the next one. But we are
  170. finished if it was the "0", and the address of the next
  171. instruction (after the message) is back on the stack, so we can
  172. just return.
  173.     That address we keep incrementing as we work through the
  174. message must be preserved each time we CALL 0005H, so we don't
  175. lose our place. But there's no problem, as it's already safely on
  176. the stack. Note that it's important to balance the stack (PUSH H
  177. again) before the RZ, otherwise the program very likely will
  178. crash as it tries to return to whatever was previously put on the
  179. stack.
  180.      Note how there really isn't any fundamental distinction
  181. between program instructions and data (in this case, text) in
  182. assembly language. They are all just bytes in memory and can be
  183. freely mixed if you know what you're doing.
  184.  
  185.  
  186. 5. Decisions, Decisions
  187.      Here is another classic subroutine, which makes it more
  188. convenient to make multiple choices. Suppose you have just typed
  189. in a number 1. . .5 in response to a menu, and the program now
  190. has to call one of SUBR1. . .SUBR5. Of course you could try to do
  191. it the hard way:
  192.  
  193.     CPI    '1'    ; Perform option 1...5
  194.     CZ    SUBR1
  195.  
  196.     ( . . . program continues . . . )
  197.  
  198.     CPI    '5'
  199.     CZ    SUBR5
  200.  
  201.      But you would have to check beforehand that the input
  202. actually was in the range 1 . . . 5 (you'd want to give an error
  203. message if it wasn't); and to make sure that each SUBRx preserves
  204. the value in "A", so that a second SUBRx doesn't execute later by
  205. accident. And even then, if there are many choices, or if you do
  206. this often, you will find using the following subroutine to be
  207. easier, and to take less space. It's also a great one for
  208. learning to use the stack. It's called CASE, and it works like
  209. this:
  210.  
  211.     CALL    CASE
  212.     DB    5    ; Number of choices in table
  213.     DW    BADNUM    ; Go here if no match
  214.     DB    '1'    ; First value in table
  215.     DW    SUBR1    ; Call this if match
  216.  
  217.     ( . . . program continues . . . )
  218.  
  219.     DB    '5'    ; Last value
  220.     DW    SUBR5    ; Call this if match
  221.  
  222.      This does all the tasks mentioned above: executes exactly
  223. one of the SUBRx according to the value in "A", or executes the
  224. code at BADNUM if it can't find a match in the table. This is
  225. much like higher-level language statements like
  226.  
  227. 350  ON X GOTO 355,600,1000,1250
  228.  
  229. and here is the code that actually does the job:
  230.  
  231. CASE:    POP    H    ; Get address of following number
  232.     MOV    B,M    ; Put number of choices into B
  233.     INX    H    ; Point ahead to no-match address
  234.     MOV    E,M    ; Put low byte of it into E
  235.     INX    H    ; Point to second byte
  236.     MOV    D,M    ; Put high byte in D (now DE=addr)
  237.     INX    H    ; Point ahead to start of table
  238. ;
  239. CASEL    CMP    M    ; Does value in "A" match entry?
  240.     JNZ    CASEN    ; If no match, skip ahead
  241.     INX    H    ; Yes, match:
  242.     MOV    E,M    ; Get address
  243.     INX    H    ; Into DE,
  244.     MOV    D,M    ; Replacing previous one,
  245.     JMP    CASEX    ; And go finish up
  246. ;
  247. CASEN    INX    H    ; No match:
  248.     INX    H    ; Skip over unused address
  249. ;
  250. CASEX    INX    H    ; Skip ahead to next data item
  251.     DCR    B    ; Count down on number of choices
  252.     JNZ    CASEL    ; Loop and try again if more left
  253.     PUSH    H    ; Put return address back on stack
  254.     XCHG        ; Get subrtn from DE into HL
  255.     PCHL        ; Go execute subroutine
  256.  
  257.      Here's what happens. We begin just like SPMSG, POPping the
  258. return address from the stack, in order to examine the following
  259. data values. First is the number of choices in the table, which
  260. is put into "B" for use as a counter.
  261.      Then comes the address of the default (no-match) subroutine,
  262. which is loaded a byte at a time into the D-E pair. (Note again
  263. that the low byte comes first, this is how 16-bit values are
  264. stored in memory.)
  265.      Then we go into a loop (CASEL), comparing the value in "A"
  266. to the one at the current position in the table. If it matches we
  267. move ahead to the corresponding subroutine address, and load that
  268. into D-E (replacing the default, which was there before). If it
  269. doesn't match, we simply skip ahead to the next data item with D-
  270. E unchanged.
  271.      Eventually we reach the end of the table (DCR B causes "B"
  272. to go to zero), and the last three instructions get executed.
  273.     The D-E registers at this point hold the subroutine address
  274. from the last match in the table (or the default, if there was no
  275. match). The H-L registers, having worked through the whole table,
  276. now point to the byte following, which is where we want to return
  277. from the subroutine. So we PUSH H, placing the return address
  278. back on the stack (this balances the POP that we began with);
  279. XCHG, to get the subroutine address into H-L, and then PCHL to
  280. jump to it.
  281.  
  282.  
  283. 6. Coming Up . . .
  284.      Now you have a good grasp of calling subroutines and using
  285. the stack. Next we'll begin to learn how to use the BDOS to read
  286. and write disk files.
  287.