Contributor: CAMERON CLARK


The non-general use registers, What are they for?

***********************************************************************
*GENERAL REGISTERS *register*               definition                *
*                  *   AX   *  accumulator                   (16 bit) *
*                  *   AH   *  accumulator high-order byte   ( 8 bit) *
*                  *   AL   *  accumulator low order byte    ( 8 bit) *
*                  *   BX   *  base                          (16 bit) *
*                  *   BH   *  base high-order byte          ( 8 bit) *
*                  *   BL   *  base low-order byte           ( 8 bit) *
*                  *   CX   *  count                         (16 bit) *
*                  *   CH   *  count high order byte         ( 8 bit) *
*                  *   CL   *  count low order byte          ( 8 bit) *
*                  *   DX   *  data                          (16 bit) *
*                  *   DH   *  date high order byte          ( 8 bit) *
*                  *   DL   *  data low order byte           ( 8 bit) *
***********************************************************************
*SEGMENT REGISTERS *register*               definition                *
*                  *   CS   *  code  segment (16 bit)                 *
*                  *   DS   *  data  segment (16 bit)                 *
*                  *   SS   *  stack segment (16 bit)                 *
*                  *   ES   *  extra segment (16 bit)                 *
***********************************************************************
*INDEX REGISTERS   *register*               definition                *
*                  *   DI   *  destination index (16 bit)             *
*                  *   SI   *  source      index (16 bit)             *
***********************************************************************
*POINTERS          *register*               definition                *
*                  *   SP   *  stack       pointer (16 bit)           *
*                  *   BP   *  base        pointer (16 bit)           *
*                  *   IP   *  instruction pointer (16 bit)           *
***********************************************************************
*FLAGS               AF, CF, DF, IF, OF, PF, SF, TF, ZF               *
 
     Once again here is the list of registers [and flags] on you 80xxx
     processor. As was said before registers AX-DX are general use;
     however they do have some other special uses. In part II we saw
     that CX is known as the counting register because in is can be
     automatically inc/dec durring a loop. It can also be used to
     repeat a single type of machine instruction. [scambling for Doc]
     The "prefix" and the instructions are as follows:
 
     Prefix = REP
 
     Instructions = Movs,Lods,Stos,Ins, or outs
 
     We already know that Mov is used to move a value to a register or
     a register value some address, but what's a (lod,sto,ins,out)?
     That's were are other registers come into play.
 
     In order to do operation like in pascal, you must use to special
     pointers to get information that is far away from the current
     code being executed. These pointers can be thought of as [es:di],
     and [ds:si]. I like to label them as

     [es:di] -> put
     [ds:si] -> get
 
     These two are used to get or put the value that they point to.
     Notice that DS is called the data segment register, and it is
     used to "get" values from where DS points. To get back to pascal,
     lets use some examples.
 
     var x : array[1..22] of byte;
     [...]
     fillchar(X,32,#0); {I may have this in reverse}
 
     This procedure takes the address of X and fills is with 32 zero
     values.
     now...
     asm
       push es
       push di
 
       mov ax,seg X
       mov es,ax
       mov di,offset X
 
       mov cx,32d  {number of times to repeat}
       mov ax,0    {the "char" we want to move}
 
       REP         {tells the processor to repeat the next repeatable}
                   {instruction CX times                             }
           stosB   {store whats in AX to [es:di] and increment di}
 
       pop di
       pop es
     end;
 
     There's alot of extra stuff there and you may want to know why.
     The non-general use registers are used by pascal and they
     _must_ remain the same value in order for pascal to run correctly.
     That's why we push and pop
 
     what's push & pop? If you know, skip the next paragraph.
     Push and pop "Push" and "pop" values onto the stack. The stack is
     part of the operation system and it is used by most calling programs.
     Misuse of the stack can cause you program to crash the computer.
     Think of the stack as like a deck of cards. IF you put a Jack, then
     and Ace, and then a King in a pile you have just pushed Jack,Ace, and
     King. Know when you pick up the cards you "pop" King,Ace,Jack. This
     is known as a "last in first out" way of storage, and any storage
     using the scheme can be called a stack. A stack must always take
     the last value first and it can't jump around. What you put into
     the stack always comes out in reverse order. That's why when I
     push ES and DI, I must Pop Di then ES. The stack is a quick way
     of storing and retrieving values.
 
     Next, whats that "Seg" and "offset"?
     These are Key words that tell the compiler to substitute at that
     position the absolute address of the Variable refrenced. It's kinda
     like pascal's Addr(variable) procedure. In order for you to access
     variables that aren't part of a procedure call, its best to use
     this method. Remeber that ES:DI must point to the variable you
     are wanting to change in the code about - so you must tell the
     compiler - "I want you to give the location of this variable, and
     not the variables value" using the segment and offset key words.
 
     Why didn't you move the segment of array X dirrectly to DS?
     Answer, you can't. You can move the value of a register to a
     segment register, but you can't move immediate values.
     Illegal
        mov DS,938d
        mov DS,seg X
        mov CS,832d
        mov ES,21h
     Legal
        mov ax,938d
        mov ds,ax
        mov ax,seg X
        mov ds,ax
     or also legal
        push 938d
        pop  DS
        push seg X
        pop  DS

Tpas -> asmb
part III      [2 of 2]

This section may have a bit too much information for now. If you don't
quite get all of it, don't worry! Most of this section will be repeated
and explain in greater detail in the next upcomming sections.


var x : array[1..22] of byte;
 
[...]
 
asm
 push es
 push di
 
 mov ax,seg X
 mov es,ax
 mov di,offset X
 
 mov cx,32d  {number of times to repeat}
 mov ax,0    {the "char" we want to move}
 
 REP         {tells the processor to repeat the next repeatable}
             {instruction CX times                             }
    stosB   {store whats in AX to [es:di] and increment di}
 
 pop di
 pop es
end;
 
  Starting from the top, we know that we should push the original
  values of registers that aren't general use [non AX-DX]. Next,
  we know that we are telling the compiler to give us the address
  that X is located at and not to give us the value of X. Segment
  registers can only be assinged values by a Register or a Pop.
 
  That bring us to CX gets the number of times we want to REPeat,
  and AX get the value we want to move to X. We are know at the
  meaty instruction and that is STOSB. STOSB does a lot at once.
 
  First,  it moves the byte located in AL to [es:di].
  Second, it increments DI so it points to the next byte of X.
  Third,  CX is decremented
  fourth, if CX in not Zero then the process is repeated
 
  When you have a simple loop like
  " for I := 1 to 32 do x[i] := 0; ";
  then, the above is a _really_quick_ way to do the same thing.
  Unlike in Part II, this loop is not as general, and cant as
  easily be used to do many things durring a loop. The REP only
  works for certain instruction and can only repeat that instruction
  CX times.
 
  "OK, now what's LOD,INS,OUT ?"
  First off, the STOS can be use two other ways. There is a STOSW
  that moves a word at a time [AX] and advances DI two bytes. The
  other one is STOS and truthfully, I haven't and don't know how
  to use it yet.
 
  LODS is kinda the oposite of STOS. IF stosB, it moves the byte at
  DS:SI into AL, and if stosW, it moves the word at DS:SI into AX.
 
  !Unless you know your hardware ports, skip INS and OUT for now.!
  ]INS is not part of the 8088 and 8086 processor and won't execute.
  ]IT takes a byte or word [insB, insW] from the port listed in DX
  ]and moves it the addess located in [ES:ID].
  ]
  ]OUT takes a byte or word and moves it to a port. It can be done
  ]as follows
  ]
  ]OUT 13h,AL  {moves byte in AL to port 13h}
  ]OUT 13h,AX  {moves word in AX to port 13h}
  ]OUT DX,al   {move byte in al to port listed DX}
  ]out DX,ax   {move word in ax to port listed DX}
  ]
  ]outs, outsB,outsW are not part of the 8088 and 8086 instruction
  ]outsB moves byte [ds:si] to port listed in DX and advances SI
  ]outsW moves word [ds:si] to port listed in DX and advances SI
  ]I've not used OUTS & don't yet know how.
 
 
  That brings us back to the rest of the special use registers,
  And they are
     CS   *  code  segment (16 bit)                 *
     SS   *  stack segment (16 bit)                 *.
 
  My best advice is DON'T TOUCH! Your processor uses pointers
  just like you so it knows what to execute next and where the
  top of the stack is. CS:IP points the next instuction to execute
  and SS:SP points to the top of the stack.
  CS:IP are changed by jumps and calls.
  SS:SP are changed by pushes and pops
 
  -------------------------------------------------------------
  I screwed up a little bit in order so I need to rectify this
  now.
 
  I need to define immediate values, effective addresses, and so forth.
 
  mov ax,983d
  An immediate value is a byte or word that is part of the instruction.
  In the example above, 938d is the immediate value that is going to
  be moved into ax.
  "mov ax,bx" has no immediate values. The value is stored in BX and
  the word associated is not part of the instruction.
 
 
  mov ax,[0000]
  An effective address can be a word value that is part of the instuction
  but is a location and an immediate value. When this is compiled
  0000 is not moved into ax, the word pointed to by cs:[0000] is moved
  into ax. Why CS? If you don't overide the segment the compiler used
  defaults. Unfortunatly I don't readily know which are which, but
  seeing that you might be a beginner, this won't be a problem that
  has to be delt with know. In other word ignore for now.
 
  mov ax,[bx]
  This has [bx] as the effective address. The value in BX is thought
  of as an address and the word pointed to by BX is moved into ax.
 
  There are several addressing modes and I will try to explain them as
  I get to them and as they are needed.
 
 
  _I HIGHLY suggest that we start out with assembler procedure and
  functions in Pascal and leave it at that_! I think that asm should
  be used only for critical procedures that are too slow in pascal
  or are things not supported by native pascal. This way you code can
  be effiecient and still easily understandable.
 
  Next section Part IV - assembler functions and procedures in pascal.

tpas -> asmb
part IV - assembler functions and procedures in pascal.
     [1 of 2]
 
     Now that we know just a little bit, let learn through example.
     Translation from tpas to asm is not an exact science. There
     are several way to do about the same structure as pascal and
     it is this reason that ASM procedures can be so efficient. You
     can completetly eliminate the legnthy and general machine code
     that pascal produces when things like overflow,range checking,
     and so forth, are not as important as speeding things up.
 
     For the first example, we are going to write a procedure in
     Tpascal that clears the screen in mode $13. We will assume
     that we are already in mode $13.
 
     mov ax,13h
     int 10h    {bios call}
 
 
 
     procedure ClearScrn13;
     var i,j : word;
     begin
       for I := 0 to 199 do
           For J := 0 to 319 do
                 mem[$a000:(i*320) + j] := 0;
 
     end;
 
 
     First off, I assume you know that in ram at $a000:0000 is
     were the vga screen memory is stored. I can be thought of
     as an array of 64,000 bytes, and you must do the Row_Major
     math to access it by Row and Column.
     For every row there are 320 bytes, in order to access [2,2]
     of a [320,200] array we must pass up 320 bytes in the first row
     and and 1 more byte to get to [2,2].
 
     In this case, who cares about rows and colums. We know the
     size of the 1 dimesional array so when can just loop 64000 times.
     This keeps us from having to do any math - which will slow every-
     thing down.
 
     procedure clearScrn13asm;
     begin
     asm
       push es  {remeber to store the values of non-general}
       push di
 
       mov ax,0a000h
       mov es,ax      {es := $a000}
       xor di,di      {di := 0}
 
       mov cx,32000d   {cx := 64000 div 2;}
       xor ax,ax      {ax := 0;}
 
       rep stosW
 
       pop di
       pop es
     end;
     end;
 
     I know, you saying "what the f..?". Remember first we point our
     "put" pointer [es:di] to where we are going to store stuff. So what
     is "xor di,di"? "Xor" or is "exclusive or". It means either A or B
     but not A and B. The truth table is as follows
 
     true  xor false = true
     false xor true  = true
     false xor false = false
     true  xor true  = false
 
     in bits
     1 xor 0 = 1
     0 xor 1 = 1
     0 xor 0 = 0
     1 xor 1 = 0
 
     So any number xor'd it self is zero. For example, 48 decimal is 30
     hexidecimal. In binary 30 is 00110000. Now if you xor each
     individual bit by using the table above you get
 
     0 0 1 1 0 0 0 0   |
 xor 0 0 1 1 0 0 0 0   |
 -------------------   v
   = 0 0 0 0 0 0 0 0
 
     For each column use the table. From left to right 0 xor 0 is 0,
     0 xor 0 is 0, 1 xor 1 is 0 and so forth.
 
     "Why not mov di,0 ? or mov ax,0 ?"
     Code size. Mov di,0 will produce 3 bytes of code and "xor di,di"
     should produce 1 byte of code. For more information, get the
     opcode list from "ftp.intel.com". It list all possible and valid
     instructions.
 
 
     "Why is is mov cx,32000 and not mov cx,64000?"
     Well If you look at the repeated instruction, is will move
     a word at a time, and a word is two bytes. So moving 32000
     words is the same as moving 64000 bytes but just much
     quicker.
 
     Remeber the "REP stosW" will automatically mov ax to es:di
     and increment di and decrement cx until repeated 32000 times.
 
 
     Know lets use an example that won't let us use the repeat prefix.
     Lets mov a virtual screen to the physical screen.
 
     type V_scrn : array[0..319,0..199] of byte;
     var V_P : ^V_scrn;
 
     procedure Paste_scrn( P : V_P );
     var i,j : word;
     begin
       for I := 0 to 199 do
           For J := 0 to 319 do
                 mem[$a000:(i*320) + j] := V^[j,i];; {or ist ^V[j,i]?}
     end;
 
part IV - assembler functions and procedures in pascal.
      [2 of 2]
 
procedure Paste_scrnASM( P : V_P );
     begin
     asm
        push es
        push di
        push ds
        push si
 
        {: Setup or Get and Put pointers :}
 
        {get}
        mov ax,word ptr P[2]  {ds:si -> V_screen }
        mov ds,ax
        mov si,word ptr P
 
        {put}
        mov ax,0a000h         {es:di -> screen}
        mov es,ax
        xor di,di
 
        mov cx,32000d         { for I := 1 to 32000 do begin }
     @looper:
        lodsW                 {  ax  := -> get } { inc si }
        stosW                 {  put := ax     } { inc di }
        loop @looper          { end;           } { dec CX }
 
        pop si
        pop ds
        pop di
        pop es
     end;
     end;
 
 
     OK lets start with "P[2]" a Pointer type is 4 bytes in legnth.
     The first 2 bytes are the offset that it points to and the
     second 2 bytes are the segment that it point to.
     The "[]" symbols also mean indexing - much like an 1 dimensional
     array in pascal. Seeing that the Segment is 2 bytes passed the
     begining of the pointer, we tell th copmiler that we are talking
     about the word 2 bytes passed the begining of the 4 byte pointer.
 
     "word ptr?"
     If we didn't tell the copmiler that we want that this is a word
     and it an effective address and not a varialbe, we'd have a
     problem. We can't use the Offset and Seg key words; becuase, we
     we want the value of the of the pointer and not its location.
 
 
 
     Pascal takes all the parameters of the procedure and
     pushes them on the stack write before a call to the procedure.
     In a procedure when you use one of those variables you are actually
     using a values that is on the stack. When you use the "VAR" keyword
     is pushes the address of the variable on the stack, and therefore
     any changes made to that variable are actually chaning the value
     of the original variable. Otherwise, is just pushes the value of
     the variable on the stack and any changes to that value are made
     only in the stack and are thrown away after completion.
 
     example
 
     Procedure test(VAR X : byte; Y,Z : byte);
     begin
     end;
 
 
 
     a:= 1;
     b:= 2;
     c:= 3;
 
     When you call test(a,b,c);
 
     It pushed all the paramters starting backwards with C
 
     STACK
     SP ->   [ addr(A) ]   a
             [    2    ]   b
             [    3    ]   c
 
     to move C's value into AX you could say
        mov ax,word ptr Z
     to move B's value into AX you could say
        mov ax,word ptr Y
     to move A's value into AX you could say
        mov ax,word ptr X[2]
        mov ds,ax
        mov si,word ptr X     {[ds:si] -> A}
        lodsB                 {ax := [ds:si] }
 
     because the value of A is not on the stack, but A's address is.
 
 
     !Back to the code!
 
     Next es:di points to the vga memory in ram.
 
     This...
 
       mov cx,32000d         { for I := 1 to 32000 do begin }
     @looper:
        lodsW                 {  ax  := -> get } { inc si }
        stosW                 {  put := ax     } { inc di }
        loop @looper          { end;           } { dec CX }
 
     sets up our loop counter CX
     lodsW gets from out virtual screen the first word and increments si 2
     bytes [word size in bytes]
     AX holds the word
 
     stosW takes the word in AX and puts it to the screen and increments
     di 2 bytes.
 
     Loop decrements CX and compares it to zero, if its not equal to zero
     it jumps to @Looper [this changes the values of CS:IP].
 
     You get all that done with just a tinty bit of speedy code.
 
 
 
     I didn't get to functions this time, but in part V I'll get to functions
     and how to acccess pascal variables like a string.
 
 
     As always, I'll be glad to answer any questions. Just one warning,_all
     the code in these section have _NOT_ been compiled and are sure to have
     errors. If you find these errors then maybe these lessons are too simple
     for you and you may be ready to start learning from an intermediate to
     advanced asm book. However, I'll try to be as unassuming as possible,
     but if I loose you on some concept, just mail me.
 
     Later
     **%CpC%**