Class of November 2, 1996


READING ASSIGNMENT:

By next class, finish chapter 5 and all of chapter 6.


LAB ASSIGNMENT FOR NEXT WEEK:

Write a program to clear the screen and display a rectangle.
The rectangle should be about 40 characters wide and 10 lines high.

A rectangle has 4 corners; plan the coordinates at which they should
be, for example 10,10; 10,50; 20,10 and 20,50.
Make a line across between the top 2 corners, then print down
the left side, across the bottom, and up along the right side.

Study DOS/BIOS calls for doing this.

Making the corners is the hard part -- you need special corner
characters -- you can find these 4 special characters in the ascii
chart in the back of the book. For example DAh is the left upper 
corner. BFh is the upper right corner.  Lower right corner is D9h.
Lower left corner is C0h.

You should not use the dash character for going across -- it's not
quite long enough. There is a special character for this, too: C4h.
Vertical line character is B3h.

He suggests a function call for a horizontal line and a function call
to make a vertical line. Parameter should be # of characters.

When you write the characters across, just write across, but when 
you write the character down, you have to scroll.

------------------------------------------------------------------------

End of Chapter 4:

Program to do the Fibonacci sequence.  Fibonacci was an Italian mathematician in the 14th or 15th century. The story goes that 
he wanted to be able to compute the # of rabbits you would have 
after a certain amount of time:
	0, 1, 1, 2, 3, 5, 8, 13, 21...
You add the last 2 numbers to get the next one (13+21 = 34)

Page 105, Exercise #11 asks you to compute this in assembler.

.data
F0	db	0	; $ here = 0
F1	db	1	; $ here = 1
fn	db	15 dup(0) ;makes 15 bytes stored with 0's stored $ here =2
ln 	db 	$-fn ; $ is a reference to the location counter here $ is 17
			; 17-2 will give you 15.


; We need to initialize the series by:
MOV Al, f1	; one byte long
MOV DI, offset F1
	; initialize CX so we know how many times to loop
MOV CX, 14
	; So, now we need to add the preceeding value, so we could say
ADDER: ADD AL, [DI-1] ; one byte long
	; if DI points to f1, then we add the number before it to get
	; the next value to put into FN
INC DI
MOV [DI], AL ; that is where di points to
	; So now, we could do a loop back to adder
LOOP ADDER

Could make a varible called ln
	ln db ($-FN)
and then put
  	mov cx, ln
so you could count how many spaces we reserved after FN
What is the value of $ ? It.s the location counter at that place,
i.e., FN+15 because the fn db 15 dup(0) operand is 15 bytes long.

Long discussion and many times over and over explanation of $ omitted.
Keep in mind, $ is only good at assembly time. If you look at the 
.lst file, you will see numbers in place of the $, and relocatable 
addresses in place of variable names.
At run time, the $ information is not available

Discussion of code segment vs. data segment to explain why $ is not
valid at runtime omitted.

Discussion of "top-down" vs. "bottom-up" program design omitted. These
are methods of program design and have nothing whatsoever to do with
the order in which statements are executed.

At this point, somebody was confused about FN. FN is simply a variable, 
any name could've been chosen. Re-explanation of variable contents vs. location, pointers and arrays omitted.

(My opinion: If you don't understand the order in which program statements
are executed at this point, you are in big trouble. You should spend 
every free minute of your life stepping through example code in the
turbo debugger.)

Back to the fibonacci sequence code:

Does the code do what we hoped it would do? We have to trace it to find 
out.
Note: there may be some typos below.
We're using registers 
CX	AL	DI		instruction and comments
--	--	--		---------------------------------------------
14				mov cx, 14
	1			mov al, 1
		1		mov di, offset f1
				ready to begin now
	1+0			adder: add al, [di-1] ; at offset 0 we have 0
		2		inc di
	1			mov [di], al ; puts a 1 into 1st byte of array
13				loop adder
				; not 0, so we go back to adder

	1+1			adder: add al, [di-1] ; at offset 1 we have 1
		3		inc di
	2			mov [di], al ; puts a 2 into 2nd byte of array
				; which is location 3
12				loop adder
				; not 0, so we go back to adder

	2+1			adder: add al, [di-1] ; at offset 2 we have 1
		4		inc di
	3			mov [di], al ; puts a 3 into 3rd byte of array
				; which is location 4
11				loop adder
				; cx is not 0, so we go back to adder

	3+2			adder: add al, [di-1] ; at offset 3 we have 2
		5		inc di
	5			mov [di], al ; puts a 5 into 4th byte of array
				; which is location 5
10				loop adder
				; cx is not 0, so we go back to addr

and so forth. It appears to work.

The above solution works well but is rather complex. Perhaps, given the model of calculation:
    f(n+1) = f(n) + f(n-1)

One could do this:
	mov di, offset fn
	mov si, offset f1
	mov ax, f1
	mov cx, 14
	adder: add ax, [si-1] ; here added previous value to ax, 
	mov [di], ax		; stored that into new location
	inc si		; incremented pointer to previous
	inc di		; incremented pointer to present one
	loop adder		; go back

Should do the same thing. You should try to trace this to validate his assertion. 

On lab 6, he gave us last week, how do you add 2 digits as a number, 
suppose you wanted to add 24 from the keyboard.
Perhaps the problem wasn't stated clearly: only put in one digit at 
a time, select digits so that the sum of them would be less than 10.

Added on to the problem is that we have to display the sum.
if you wanted to display a 7, in memory, you have
00000111, not a good thing to display, won't display as "7"
Must have 00110111 to display a digit (0011 is 3, all the ascii numbers start at 30h)
So if you have in 1st 4 bits of memory a 0h, it doesn't display anything

After computing sum, you have to change it to an ascii value, so
every digit has 2 parts
3 and Digit
0011 (the 3 on the left) is called the zone
must have a 3 there to make it appear as an ascii character. Can get 
that 3 there by:
OR AL, 30h
You could just add 30h, but "oring" is more elegant. And much faster 
than adding.

If the sum is more than 10, this would be more difficult. So, you 
should try to make the sum be less than 10, by inputting small 
numbers and some 0's when you run the code.

Suppose you had more than 1 digit to display.
Suppose the number was 237h that you wanted to display.
If you isolate each digit changing them into 32, 33, and 37, you 
can display them as a string
That involves a lot of code. We will not be doing that now.

---------------------------------------------------------------------------

Last week we talked about equ and = operators.
So, in my code I could write
	a = 3
3 is the redefinable =
equ is constant equal -- value defined with this operator is unchangable.

If I say A equ 3, anyplace I have an A, it will be replaced by a 3 at assemble time.

"Multiply defined symbol" is the error you get if you try to do equ 
again to the same symbol, for example if you say:
	a equ 3
	a equ "q"

But for =, I could have for example
B = 7
MOV AX, B
B=6
MOV CX, B

We have then for expressions in assembler we have the following symbols 
we can use:
( )
+ - 	unary
* /
mod
+ -
boolean and or not xor

add ax, (f1-A)*B and X
at assembler time, (f1-A)*B and X will resolve to a number to be added 
to AX

Long discussion of how a negative number is represented has been omitted.
This was discussed in great detail in many early sessions of the class!

Often when you are doing complex references to locations, you need power of this kind of computation to save you time. 

--------------------------------------------------------------------------

So we have a number of different operators in assembler. 3 of them are:
Offset	example: mov d1, offset symbol gives us offset from whatever
			segment the symbol occurs in where the symbol appears
ptr		operator for determining if the operand is a byte, word,
			doubleword, etc.
label		is an operator which will 
		ptr16 label word: sets a symbol 16 bits, 2 bytes or one word
			of memory and because we're defining a label, it takes
			up no space -- it is a symbol pointing to one word.
		if I have ptr32 dd, we get a 32 bit 4 byte part of memory
		ptr32 points to beginning of that part of memory.
		Could have another ponter to same spot

If I say, for example,
	mov di, ptr16	; offset is not needed
	moves into di, the address = to the offset of ptr32 into di.
	Does this by location counter pointing to next available byte, which
	is given at ptr16 at assemble time. since ptr16 takes up no space, lcation conter is not incrementd, so when ptr32 is assigned it points 
to the same space. But ptr16 points only to the first 2 bytes worth of 
stuff pointed to by ptr32.
You might want to do something like this:
	mov di, word ptr ptr32
but it would be more convenient to say
	mov di, ptr16
and these 2 instructions will do exactly the same thing. 
But if I write
	mov di, ptr32 ; ptr32 is pointing to a double word, so it won't
; assemble because di is not big enough to hold double word di is only
; 2 bytes one word
; i.e., a label is an automatic pointer.
	mov di, word ptr ptr32 ; makes ptr32 serve up a one word length to
		; di so that it can fit -- only the 1st word of ptr32 goes in
q: what if you said
	ptr16 label byte
a: then you could:
	mov al, ptr16
if he left is as ptr16 label word, then he would have to
	mov al, byte ptr ptr16
			
seg	will give you the segment value of the symbol 
	He doesn't want to talk about this operator as we will not use it
	in this class "Beyond the scope"

ptr can override a symbol's default length attribute.

-------------------------------------------------------------------------

JMP instruction

jmp symbol or
jmp label

(There's a label which is an operator and a label which is a symbol)
symbol that serves as a labsl is defined as, say:
adder:
defines the symbol adder with a value equal to the location counter 
at this point in the code.

So:
.code
L1: mov ax, @data	 ; location counter is 0; L1= 0
L2: mov ds, ax	 ; L2 = 0003 if the prior instruction was 3 bytes 
 			 ; long (he is not 100% sure)

Later on in the code:

JMP L1	; it would take its next execution from that place.
JMP L2	; it would go to the line that says mov ds, ax

The JMP instruction can jump different distances. There are a number of differnt JMP instructions. JMP can jump to a location that is very near (called "short") also:
		JMP to a near ptr
		JMP to a far ptr

example of JMP (short) between -128 to +127 bytes away.
L1: ; stuff
	; bunch of lines of code
jmp L1 ; if distance between L1 and here is less than 128, then this 
will be a short jump.
1 byte can have a value between 0 and FFFF 

Short jmp uses a signed 8 bit value to determine how far it jumps.

ADDER: ADD AX, 1 ;instruction is 3 bytes long
JMP ADDER	; same as jmp $-3
	; it will put in a value of FD
	; 

This obviously cause a continuous loop...

jmp ahead		; probably jumps about 6 bytes ahead.
add ax, 1
ahead: sub ax, 1

A short JMP changes the value in IP register.
Adds or subtracts a value to the IP register.
A short JMP modifies the IP register by a factor contained in a 
signed 8 bit value.

A near JMP places a 16 bit value into the IP register.
Greater than 128 bytes away.

Brings up a question of the way you write your code. 
  ; 1 line
; 2 lines
BACK:
; 3 lines
JMP AHEAD ; what is the distance to AHEAD? if you know it is less
	; than 128 bytes, you can say
	JMP SHORT AHEAD
	; This tells assembler that ahead is less than 128 bytes away.
	; If you already passed the symbol and you want to jump back, it
	; knows what kind of a jump it is, for example:
	JMP BACK
; If further away, than 127 and less than 32767 you would say
	jmp near ptr ahead ; NEAR JUMP IS 1 MORE BYTE OF CODE
; n lines
	
AHEAD: 




JMP is an absolute, not conditional JMP. -- It will go there no 
matter what.
The other kind of JMP is a conditional JMP, for example,
JNE depends on a particular value in flag resigster

JMP far pointer will take you to another segment -- you could jump 32 megabytes away if you wanted to. You would probably never do a far 
jump in "straight" assembler only if you were linking with a higher 
level routine in pascal or C for example.

A Far call is not necessarily the same as a far jump.

When we get to linking between C and ASM (before the end of the term), 
we use a far call so we can get back to where we came from. But a far 
call is not really any different functionally from a near a call. A far 
call is a call to a routine that is in a different code segment.

You would not use JMP to invoke a function or subroutine.

--------------------------------------------------------------------------
Input/Output

I/O is generally done using DOS or BIOS functions.
One could write one's own I/O functions, but it takes a trememdous amount 
of code, much easier to use DOS call.
The first zero to 1000 bytes of memory contain a transfer vector. When 
you say INT 21h
This points somewhere into the transfer vector -- into a pointer to a DOS function in there. The DOS function does its thing, then calls RINT which sends you back to the next line of your code. 

DOS functions look at contents of registers to find out what you want.

When you do INT 21, you will have to have loaded AL and maybe other registers to inform DOS.

For example, to display a string, we could:
.data
	str db "This is a string"
	len dw $-str
.code
	; normal initialization here
 	MOV CX, LEN
	MOV SI, OFFSET STR
GETCHR: MOV AH, 2 ; to dos, means display a character
	MOV DL, [SI]; give character to dos using DL to hold it
	INT 21h	; dos call to display 1 character.
	INC SI	
LOOP GETCHR

puts address of "T" into SI, then put the "T" into DL,
put a 2 into AL and then call DOS, which will display a "T"
Next, time, it will display the "h"

In preparation for these DOS and BIOS calls


Procedures 
Suppose we have

.code
main proc
	; some code
	mov ax, 1
	mov dx, ax
	call call_me
	mov ax, cx
	; more code
main endp

; here is a function
call_me proc
	; code
	mov cx, 2
	mov dx, 3
	ret ; go back to where you got called from
call_me endp

when the call instruction executes, this:
	pushes contents of IP onto the stack
	remember IP already points to the next instruction
		(mov ax, cx in this example)
	makes IP get the value that is the offset of call_me
	Then control is sent to the first line of call_me

ret causes:
	POP IP
	so stack value is placed into IP
		in our example, goes to line of code that says mov ax, cx

Be very careful in your functions not to POP too many or too few times
or you will not return to the proper place! "Lost forever" if you don't
guard the stack. # of POPs must = # of PUSHs

Protocol for subroutines, functions, procedures 
1. Keep stack "balanced" 
	# PUSH MUST = # POP
2. Save and restore whatever registers you use in the subroutine.
3. Use the stack to hold these values.
4. How to get data to and from subroutine?
   Communication to a subroutine is done by:
	Use registers holding data, 
	addresses (offset only or segment and offset), pointers
	Use the stack
   Can't access the variables of your calling proc if in a different segment!
	External symbol must be used to do this.