Government Security
Network Security Resources

Jump to content

Photo

9 Part Tutorial On Assembly


  • Please log in to reply
13 replies to this topic

#1 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 01:58 PM

I have read through alot of this about 2 years ago.

They way that they are structured is easy for a beginner to understand. It will take alot of work if you know nothing about the topic to pick it up.
The Original Archive that he is talking about in his copyright is a zip file containing these txt documents, I havnt changed or edited anything.
I do not take credit for anything but the post :D

Thanks and Enjoy
"To try is to fail....so to succeed is to be lucky"

#2 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:00 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART I
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.4
Date : 16-02-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

What is Assembler?
--------------------

Assembler has got to be one of my favourite languages to work with. Not that
it's an easy language at first, but when you become familiar with it, you'll
realise just how logical it is.

Assembler is a low-level language which you can use to give you programs added
speed on slow tasks. Basically it consists of statements which represent
machine language instructions, and as it's nearly machine code, it's fast.

In the early days before the 8086 came about, yes, there were humans on the
Earth back then, :), programming was not an easy task. When the first
computers were developed, programming had to be done in machine code which was
_not_ an easy task, and so Assembler was born.


Why use it?
-------------

As I said before, Assembler is fast. It also allows you to speak to the
machine at hardware level, and gives you much greater control and flexibility
over the PC. One of the other advantages of Assembler is that it allows you
to impress your friends by entering pages of seemingly incomprehensible code.
Watch them gather round you and be impressed/laugh at your nerdiness? :)


How did this tutorial come about?
-----------------------------------

Well, I had a couple of friends who wanted to learn Assembler to speed up
their Pascal programs, so I gave them some Assembler Tutorials I had. While
these tutorials had all the information you'd ever need, they were not written
for the novice to easily understand, so I decided to write my own.

If you're using this tutorial and find it useful and informative, then please
mail me. I appreciate feedback.



LESSON 1 - Registers
----------------------

When you're working with Assembler, you'll have to use registers. You can
think of these as variables already defined for you. The most common are
listed below:

AX - the accumulator. Comprises AH and AL, the high and low bytes
of AX. Commonly used in mathematical and I/O operations.

BX - the base. Comprises BH and BL. Commonly used as a base or
pointer register.

CX - the counter. Comprises CH and CL. Used often in loops.

DX - the displacement, similar to the base register. Comprises DH and
DL. I think you're getting the pattern now.

These registers are defined as general purpose registers as we can really
store anything we want in them. They are also 16-bit registers, meaning that
we can store a positive integer from 0 to 65535, or a negative integer from
-32768 to 32768.

Incidently, the matter of the high and low byte of these resgisters has caused
quite a bit of confusion in the past, so I'll try to give it some explaination
here. AX has a range of 0 to FFFFh. This means that you have a range of
0 to FFh for AH and AL. (If you're a little concerned with the hex, don't
worry. Next tutorial will cover it.)

Now if we were to store 0A4Ch in AX, AH will contain 0Ah, and AL will contain
4Ch. Get the idea? This is a pretty important concept, and I'll cover it in
more depth next tute.


The segment registers: - ta da!

These are some other registers which we will not cover for the first few
tutorials, but will look at in greater depth later. They are immensely handy,
but can also be dangerous.

CS - the code segment. The block of memory where the code is stored.
DON'T fool around with this one unless you know what you are doing.
I'm not all that sure that you can actually change it - I've never
tried.

DS - the data segment. The area in memory where the data is stored.
During block operations when vast blocks of data are moved, this is
the segment which the CPU commonly refers to.

ES - the extra segment. Just another data segment, but this one is
commonly used when accessing the video.

SS - no, not the German army. This is the stack segment, in which the
CPU stores return addresses from subroutines. Take care with this
one. :)

Some others you will commonly use are:

SI - the source index. Often used in conjuction with block move
instructions. This is a pointer within a segment, usually DS, that
is read from by the CPU.

DI - the destination index. Again, you'll use it a lot. Another pointer
within a segment, often ES, that is written to by the CPU.

BP - the base pointer, used in conjunction with the stack segment. We
won't be using it a lot.

SP - the stack pointer, commonly used with the stack segment. DON'T fool
around with this one until you are sure you know what you are doing.

By now you should understand what registers are. There are other registers
too, and things known as flags, but we will not go into these as yet.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

THINGS TO DO:

1) Learn the various registers off by heart.
2) Get a calculator that supports hexadecimal - damn handy, or a least an
ASCII chart. That covers 0 - 255, or 0h to FFh.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

LESSON 2 - The 8086 instruction set:
--------------------------------------

Okay, so you've learnt about registers, but how do you use them, and how do
you code in Assembler? Well, first you'll need some instructions. The
following instructions can be used on all CPU's from the 8086 up.

MOV <dest>, <value> - MOVE. This instruction allows you to MOVE a value
into a location in memory.

EG: MOV AX, 13h

This would move 13h (19 decimal) into the AX
regsister. So if AX had previously held 0, it
would now hold 13h.

THIS ONLY MOVES THE VALUE INTO THE REGISTER, IT
DOES NOT DO ANYTHING.

EG: (In Pascal) AX := $13;

INT <number> - INTERRUPT. This instruction generates an interupt.
You can think of this as a bit like a procedure.

EG: INT 10h

Would generate interrupt 10h (16 decimal). Now
what this would do depends on the contents of the
AH register, among other things. For instance,
if AX = 13h and interrupt 10h was generated, the
video would be placed into 320x200x256 mode.

More accurately:

AH would equal 00 - set mode subfunction, and
AL would equal 13h - 320x200x256 graphics mode.

However, if AH = 2h, and interrupt 16h was
generated, this would instruct the CPU to check if
a keypress was waiting in the keyboard buffer.

If AH = 2h, and BH = 0h and interrupt 10h was
generated, then the CPU would move the cursor to
the X location in DL and the Y location in DH.

You can bear in mind, that AH contains the function
to execute, and the other registers may contain any
other data necessary.

DO NOT WORRY ABOUT THIS FOR NOW, WE WILL COVER IT
IN GREATER DETAIL LATER.

ADD <dest> <value> - ADD. This instruction adds a number to the value
stored in dest.

EG: MOV AX, 0h ; AX now equals 0h
ADD AX, 5h ; AX now equals 5h
ADD AX, 10h ; AX now equals 15h

Pretty simple, huh?

SUB <dest> <value> - SUBTRACT. I think you can guess what this does.

EG: MOV AX, 13h ; AX now equals 13h (19 dec)
SUB AX, 5h ; AX now equals 0Eh (14 dec)

DEC <register> - DECREMENTS something.

EG: MOV AX, 13h ; AX now equals 13h
DEC AX ; AX now equals 12h

INC <register> - INCREMENTS something.

EG: MOV AX, 13h ; Take a guess
INC AX ; AX = AX + 1

JMP <location> - JUMPS to a location.

EG: JMP 020Ah ; Jump to the instruction at 020Ah
JMP @MyLabel ; Jump to @MyLabel.

DON'T WORRY IF THIS IS A LITTLE CONFUSING - IT GETS
WORSE! THERE ARE 28 DIFFERENT JUMP INSTRUCTIONS TO
LEARN, MAYBE MORE. WE'LL COVER THEM LATER.

CALL <procedure> - CALLS a subfunction.

EG: Procedure MyProc;

Begin { MyProc }
{ ... }
End; { MyProc }

Begin { Main }
Asm
CALL MyProc ; Guess what this does!
End;
End.

OR: CALL F6E0h ; Call subfunction at F6E0h

LOOP <label> - LOOPS for a period of time.

EG: MOV CX, 10h ; This is why CX is called the
; COUNT register. 10h = 16

@MyLabel:

; some stuff
; more stuff

LOOP @MyLabel ; Until CX = 0
; Note: CX gets decremented
; each time. Don't DEC it
; yourself.

; THIS WOULD LOOP 16 times - thats 10 in hex.


LODSB - Load a byte
LODSW - Load a word
STOSB - Store a byte
STOSW - Store a word

These instructions are used to put or get something in a location in
memory. The ES:SI register, (remember we talked about this earlier as
SI being the source index?), points to the location we want to get data
from, and ES:DI points to where we will be putting information.

Anyway, imagine that we have the following set-up in memory:

Memory Location 06 07 08 09 10 11 12
陳陳陳陳陳陳陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳
Value 50 32 38 03 23 01 12

When we use LODSB or STOSB, it returns or gets a number in AL. So if
ES:SI pointed to 07 and we executed a LODSB instruction, AL would now
equal 32.

Now, if we pointed ES:DI to 11, put say, 50 in the AL register and
executed STOSB, then the following would result:

Memory Location 06 07 08 09 10 11 12
陳陳陳陳陳陳陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳
Value 50 32 38 03 23 50 12

NOTE: When we use LODSB/STOSB we use AL. This is because we will be
dealing with an 8-bit number, (a byte) only. We can store an
8-bit number in AL, AH, or AX, but we cannot store a 16-bit
number in AH or AL because these are 8-BIT REGISTERS.

As a result, when we uses LODSW or STOSW, we must use AX and not
AL, as we will be getting/putting a 16-bit number.


MOVSB - Move a byte
MOVSW - Move a word

As an example we'll get a byte from DS:SI and send it to ES:DI.

At DS:SI:

Memory Location 06 07 08 09 10 11 12
陳陳陳陳陳陳陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳
Value 50 32 38 03 23 50 12


At ES:DI:

Memory Location 06 07 08 09 10 11 12
陳陳陳陳陳陳陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳
Value 10 11 20 02 67 00 12

If point DS:SI to location 07, point ES:SI to location 11 and
execute MOVSB, the stuff at ES:DI will look like:

At ES:DI:

Memory Location 06 07 08 09 10 11 12
陳陳陳陳陳陳陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳
Value 10 11 20 02 67 32 12


I HOPE YOU GET THE GENERAL IDEA. HOWEVER, OF COURSE IT ISN'T THAT
SIMPLE. MEMORY LOCATIONS AREN'T ARRANGED IN ARRAY FORM, ALTHOUGH I WISH
THEY WERE. WHEN MOVING/GETTING/PUTTING YOU BE DEALING WITH A SEGMENT/
OFFSET LOCATION.

REP - REPEAT for the number of times specified in the CX register.
A REP in front of a MOVSB/LODSB/STOSB instruction would cause that
instruction to be repeated. So:

If CX = 5, and
if ES:DI pointed to 1000:1000h,

then REP STOSB would store what was in the AL register in the
location 1000:1000h 5 times.

THINGS TO DO:

1) Memorise all the instructions above - it's not hard and there's not many
there.

2) Make sure you understand the theory behind it.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

COMING UP NEXT WEEK:
----------------------

Hexadecimal and what it is.

Segments and offsets - we touched on them in this tute.

Some more instructions.

Some sample programs, and code you can use in your programs.

Maybe a PutPixel, ClrScr, anything I think is useful.

If you wish to see a topic discussed in a future tutorial, then mail me, and
I'll see what I can do.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Don't miss out!!! Download next week's tutorial from my homepage at:

http://www.faroc.com.au/~blackcat


- Adam.
"To try is to fail....so to succeed is to be lucky"

#3 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:02 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART II
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.4
Date : 17-02-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Hello again, budding Assembler programmers. For those who missed the first
issue, get it now at my homepage.

Anyway, last issue I said we'd be discussing hexadecimal, segments + offsets,
some more instructions and some procedures containing assembler that you could
actually use.

So, here we go with segments and offsets!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

LESSON 3 - Segments and Offsets
---------------------------------

Before we delve into the big, bad world of segments and offsets, there is some
terminology you'll need to know.

The BIT - the smallest piece of data we can use. A bit - one eigth of
a byte can be either a 1 or a 0. Using these two digits we can make up
numbers in BINARY or BASE 2 format.

EG: 0000 = 0 0100 = 4 1000 = 8 1100 = 12 10000 = 16
0001 = 1 0101 = 5 1001 = 9 1101 = 13 ...I think you
0010 = 2 0110 = 6 1010 = 10 1110 = 14 get the idea...
0011 = 3 0111 = 7 1011 = 11 1111 = 15

The NIBBLE, or four bits. A nible can have a maximum value of 1111 which
is 15 in decimal. This is where hexadecimal comes in. hex is based on
those 16 numbers, (0-15), and when writing hex, we use the 'digits'
below:

0 1 2 3 4 5 6 7 8 9 A B C D E F

Hexadecimal is actually quite easy to use, and just as a 'fun fact', I
think the Babylonians - some ancient civilisation anyway - used a BASE-16
number system. Any historians out there who want to confirm this?

IMPORTANT >>> A nibble can hold a value up to Fh <<< IMPORTANT

The BYTE - what we'll be using most. The byte is 8 bits long - that's 2
nibbles, and is the only value you'll be able to put in one of the 8-bit
registers, EG: AH, AL, BH, BL, ...

A byte has a maximum value of 255 in decimal, 11111111 in binary, or FFh
in hexadecimal.

The WORD - another commonly used unit. A word is a 16-bit number, and
is capable of holding a number up to 65535. That's 1111111111111111 in
binary, and FFFFh in hex.

Note: Because a word is four nibbles, it is also represented by four
hexadecimal figures.

Note: This is a 16-bit number, and this corresponds to the 16-bit
registers. That's AX, BX, CX, DX, DI, SI, BP, SP, DS, ES, SS
and IP.

The DWORD, or double word consists of 2 words or 4 bytes or 8 nibbles or
32-bits. You will not use double words much in these tutorials, but
we'll mention them later when we cover 32-BIT PROGRAMMING.

A DWORD can hold from 0 to 4,294,967,295, that's FFFFFFFFh, or
11111111111111111111111111111111. I hope there's 32 one's back there.

The DWORD is also the size of the 32-BIT extended registers, or EAX,
EBX, ECX, EDX, EDI, ESI, EBP, ESP and EIP.

The KILOBYTE, is 1024 bytes, _NOT_ 1000 bytes. The kilobyte is equal to
256 double-words, 512 words, 1024 bytes, 2048 nibbles or 8192 BITS. I'm
not going to write out all the one's.

The MEGABYTE, or 1024 kilobytes. That's 1,048,576 bytes or 8,388,608
bits.

Now we've covered the terminology, let's have a closer look at just how those
registers are structured. We said that AL and AH were 8-bit registers, so
shouldn't they look something like this?

AH AL
敖陳堕堕堕堕堕堕堕陳 敖陳堕堕堕堕堕堕堕陳
00000000 00000000


In this case, both AH and AL = 0, OR 00h and 00h. As a result, to work out
AX we use: AX = 00h + 00h. When I say + I mean, 'just put together' not
AX = AH PLUS AL.

So, if AH were to equal 00000011 and AL were to equal 0000100, to work out
AX we must do the following.

1) Get the hex values for AH and AL.

00000011 = 03h 00010000 = 10h

2) Combine them.

AX = AH + AL
AX = 03h + 10h
AX = 0310h

And there you have it. Not too tricky.

Okay, now lets look at a 16-bit register:


AX
敖陳陳陳陳陳陳陳陳陳陳陳


AH AL
敖陳堕堕堕堕堕堕堕陳 敖陳堕堕堕堕堕堕堕陳
00000000 00000000

So from that, we can see that AX = 00000000 and 00000000, or 0000000000000000.


Now lastly, lets see what a 32-bit register looks like:

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕
EAX
団陳陳陳陳陳陳陳陳陳陳陳
AX
団陳陳陳陳陳堕陳陳陳陳陳
00000000 00000000 00000000 00000000
AH AL
青陳陳陳陳陳祖陳陳陳陳陳祖陳陳陳陳陳陳陳陳陳陳陳潰

Not too difficult either, I hope. And if you got that, you're ready for
SEGMENTS and OFFSETS.


A Segmented Architechture
----------------------------

Long, long ago, when IBM built the first PC, it wasn't feasible for programs
to be above 1 megabyte - heck, the first XT's had only 64K of RAM! Anyway,
seeing as the designers of the XT didn't envisage huge applications, they
decided split memory up into SEGMENTS, measily small areas of RAM which you
can JUST fit a virtual screen for 320x200x256 graphics mode in.

Of course, you can access more than a megabyte of RAM, but you have to split
it up into segments to use it, and this is the problem. Of course, with
32-bit programming you can access up to 4GB of RAM without using segments, but
that's another story.

Segments and offsets are just a method of specifying a location in memory.


EG: 3CE5:502A

^^^^ ^^^^
SEG OFS

Okay, here's the specs:


An OFFSET = SEGMENT X 16
A SEGMENT = OFFSET / 16

Some segment registers are:

CS, DS, ES, SS and FS, GF - Note: The last 2 are 386+ registers.

Some offset registers are:

BX, DI, SI, BP, SP, IP - Note: When in protected mode, you can use any
general purpose register as an offset
register - EXCEPT IP.


Some common segments and offsets are:

CS:IP - Addres of the currently executing code.
SS:SP - Address of the current stack position.

NOTE: DO NOT TAMPER WITH THESE!

So when we refer to segments and offsets, we do so in the form:

SEGMENT:OFFSET

A good example would be:

A000:0000 - which actually corresponds to the top left of the VGA screen in
320x200x256 color mode.

** FUN FACT ** VGA RAM starts a A000h :)


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Phew! That was a lot for the second tute. However, we're not done yet. The
AX, AH, AL thing is a concept you may not have grasped yet, so here we go:

MOV AX, 0 ; AX = 0
MOV AL, 0 ; AL = 0
MOV AH, 0 ; AH = 0

MOV AL, FFh ; AL = FFh
; AX = 00FFh
; AH = 00h

INC AX ; AX = AX + 1

; AX = 0100h
; AH = 01h
; AL = 00h

MOV AH, ABh ; AX = AB00h
; AH = ABh
; AL = 00h


Got it yet?


THINGS TO DO:

1) Learn the BIT/NIBBLE/BYTE... stuff off by heart.
2) Go back over the segment and offset examples.
3) Make sure you understand the relationship between AX, AH and AL.
4) How about some hex addition problems?


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

The Stack
-----------

The stack is a very useful feature which we can take advantage of. Think of
it as stack of papers in an IN tray. If you put something on the top, it'll
be the first one taken off.

As you add something to the stack, the stack pointer is DECREASED, and when
you take it off, it is INCREASED. To explain this better, look at the
diagram below:

敖陳陳陳陳陳陳陳陳朕
The STACK
団陳陳陳陳陳陳陳陳調
<<< When PUSHing a byte onto the stack, it goes
here - last on, first off.






SP <<< The stack pointer moves downward.
青陳陳陳陳陳陳陳陳潰


And in practice:


MOV AX, 03h ; AX = 03h
PUSH AX ; PUSH AX onto the stack

MOV AX, 04Eh ; AX = 04Eh

; Do anything...perform a sum?

POP AX ; AX = 03h

Or:

MOV AX, 03h ; AX = 03h
PUSH AX ; Add AX to the stack

MOV AX, 04Eh ; AX = 04Eh

; Do anything...perform a sum?

POP BX ; BX = 03h


You've just learnt two new instructions:

PUSH <register> - PUSHes something onto the stack, and

POP <register> - POPs it back off.


That's all you'll need to know about the stack - for now.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

And lastly, some procedures which demonstrate some of this stuff. Note that
the comments have been DELIBERATELY REMOVED. It is your task to try and
comment them, and by comment I just mean write down what each instruction is
doing. Note also, that some new instructions are introduced.


Procedure ClearScreen(A : Byte; Ch : Char); Assembler;

Asm { ClearScreen }
mov ax, 0B800h
mov es, ax
xor di, di
mov cx, 2000
mov ah, A
mov al, &Ch
rep stosw
End; { ClearScreen }


Procedure CursorXY(X, Y : Word); Assembler;

Asm { CursorXY }
mov ax, Y
mov dh, al
dec dh
mov ax, X
mov dl, al
dec dl
mov ah, 2
xor bh, bh
int 10h
End; { CursorXY }


Procedure PutPixel(X, Y : Integer; C : Byte; Adr : Word); Assembler;

Asm { PutPixel }
mov ax, [Adr]
mov es, ax
mov bx, [X]
mov dx, [Y]
xchg dh, dl
mov al, [C]
mov di, dx
shr di, 2
add di, dx
add di, bx
stosb
End; { PutPixel }


Procedure Delay(ms : Word); Assembler;

Asm { Delay }
mov ax, 1000
mul ms
mov cx, dx
mov dx, ax
mov ah, 86h
int 15h
End; { Delay }


THINGS TO DO:

1) Go over the stack example. Make your own code example.
2) Comment the procedures above as best as you can. Try and guess what the
new instructions do. It's not that hard.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


COMING UP NEXT WEEK:
----------------------

Many more instructions, all the JUMPS.

What are flags?

The above procedures with comments.

An assembler-only program. You'll need DEBUG at least,
though TASM and TLINK would be a good idea.



If you wish to see a topic discussed in a future tutorial, then mail me, and
I'll see what I can do.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Don't miss out!!! Download next week's tutorial from my homepage at:

http://www.faroc.com.au/~blackcat


- Adam Hyde.
"To try is to fail....so to succeed is to be lucky"

#4 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:06 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART III
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.3
Date : 27-02-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Welcome to the third tutorial in the series. Last tutorial I said we'd be
discussing some more instructions, flags and an actual assembler program.

During this tutorial, you will find "Peter Norton's Guide to Assembler",
"Peter Norton's Guide to the VGA Card", or any of the "Peter Norton's Guide
to..." books damn handy. You cannot program in Assembler without knowing
what all the interrupts are for and what all the subfunctions are.

I recommend you obtain a copy as soon as possible.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

An Assembler Program
----------------------

I don't generally write code in 100% Assembler. It is much more convenient
to use a high level language such as C or Pascal, and use Assembler to speed
up the slow bits. However, you may wish to torture yourself and write an
application completely in Assembler, so here is the basic template:

敖陳陳陳陳陳陳陳
DOSSEG - tells the CPU how to sort the segment. CODE, DATA + STACK

MODEL - declare the model we will use

STACK - how much stack will we allocate?

DATA - what's going into the data segment

CODE - what's going into the code segment

START - the start of your code

END START - the end of your code
青陳陳陳陳陳陳陳

FUN FACT: I know of someone who wrote a Space Invaders clone, (9K), all
in Assembler. I have the source if anyone is interested...

Okay, now let's look at a sample program that'll do absolutely nothing!

DOSSEG ; Not really necessary
.MODEL SMALL
.STACK 200h
.DATA
.CODE

START:
MOV AX, 4C00h ; AH = 4Ch, AL = 00h
INT 21h
END START

Let's go over this in more detail. Below, each of the above statements are
explained.

DOSSEG - this sorts the segments in the order:

Code segments;
Data segments;
Stack segments.

Not really necessary, but leave it in while you are
learning.

MODEL - this allows the CPU to determine how your program is
structured. You may have the following MODELs:

1) TINY - both code and data fit in the same 64K
segment.

2) SMALL - code and data are in different segments, though
each are less than 64K.

3) MEDIUM - code can be larger than 64K, but data has to be
less than 64K.

4) COMPACT - code is less than 64K, but data can be greater
than 64K.

5) LARGE - code and data may be larger than 64K, though
arrays cannot be greater than 64K.

6) HUGE - code, data and arrays may be larger than 64K.

STACK - this instructs the PC to set up a stack as large as the
amount specified.

DATA - allows you to create a data segment.

Basically, where all your data will go.

CODE - allows you to create a code segment.

Basically, where all your code will go.

START - Just a label to tell the compiler where the main body of
your program begins.

MOV AX, 4C00h ; AH = 4Ch, AL = 00h

This moves 4Ch into ah, which coincidently returns us to DOS.
When interrupt 21h is called and AH = 4Ch, back to DOS we go.

INT 21h

END START - Do you have no imagination?

Okay, I hope you got all that, because now we're actually going to do
something. Excited yet? :)

In this example we'll be using interrupt 21h, (the DOS interrupt), to print a
string. To be precise, we'll be using subfunction 9h, and it looks like
this:

INTERRUPT 21h
SUBFUNCTION 9h

Requires:

AH = 9h
DS:DX = FAR pointer to the string to be printed. The string must be
terminated with a $ sign.

So here's the example:

DOSSEG
.MODEL SMALL
.STACK 200h
.DATA

OurString DB "This is a string of characters. "
DB "Do you lack imagination? Put something interesting here!$"

.CODE

START:
MOV AX, SEG OurString ; Move the segment where OurString is located
MOV DS, AX ; into AX, and now into DS

MOV DX, OFFSET OurString ; Offset of OurString -> DX
MOV AH, 9h ; Print string subfunction
INT 21h ; Generate interrupt 21h

MOV AX, 4C00h ; Exit to DOS sufunction
INT 21h ; Generate interrupt 21h
END START


If you assemble this with TASM - TASM WHATEVERYOUWANTTOCALLIT.ASM then link
with TLINK - TLINK WHATEVERYOUCALLEDIT.OBJ you'll get an EXE file of about
652 bytes. You can use these programs in DEBUG with some modifications, but
I'll leave that up to you. To work with standalone Assembler you _need_
TASM and TLINK, though I guess MASM <shudder> would do the same job OK.

Now lets go over the code in a bit more detail:

MOV AX, SEG OurString ; Move the segment where OurString is located
MOV DS, AX ; into AX, and now into DS

MOV DX, OFFSET OurString ; Move the offset where OurString is located
MOV AH, 9h ; Print string subfunction
INT 21h ; Generate interrupt 21h

You'll notice we had to use AX to put the segment address of OurString in DS.
You will discover that you cannot refer to a segment register directly in
Assembler. In last tute's PutPixel procedure, I moved the address of the VGA
into AX, and then into ES.

The SEG instruction is also introduced. SEG returns the segment of where the
string OurString is located, and OFFSET returns, guess what?, the offset from
the beginning of the segment to where the string ends.

Notice also that we used DB. DB is nothing special, and stands for Declare
Byte, which is all it does. DW, Declare Word and DD, Declare Double word also
exist.


You could have also put OurString in the code segment, the advantage being
CS will be pointing to the same segment as OurSting, so you wont have to
worry about finding the segment which OurString lies in.

The above program in the code segment would look like:

DOSSEG
.MODEL SMALL
.STACK 200h
.CODE

OurString DB "Down with the data segment!$"

START:
MOV AX, CS
MOV DS, AX

MOV DX, OFFSET Message
MOV AH, 9
INT 21h

MOV AX, 4C00h
INT 21h
END START

Simple, no?

We won't look at standalone Assembler programs again all that much, but most
of the techniques we'll be using can be implemented in the basic Assembler
standalone template.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

So, what are flags?
---------------------

This part's for my mate Clive who's been hassling me about flags for a while,
so here we go Clive, with FLAGS.

I can't remember if we introduced the CMP instruction or not, CMP - (COMPARE),
but CMP compares two numbers and reflects the comparison in the FLAGS. To
use it you'd do something like this:

CMP AX, BX

then follow with a statement like those below:


UNSIGNED COMPARISONS:
------------------------

JA - jump if AX was ABOVE BX;
JAE - jump if AX was ABOVE or EQUAL to BX;
JB - jump if AX was BELOW BX;
JBE - jump if AX was BELOW or EQUAL to BX;
JNA - jump if AX was NOT ABOVE BX;
JNAE - jump if AX was NOT ABOVE or EQUAL to BX;
JNB - jump if AX was NOT BELOW BX;
JNBE - jump if AX was NOT BELOW or EQUAL to BX;
JZ - jump if ZERO flag set - same as JE;
JE - jump if AX is EQUAL to BX;
JNZ - jump if ZERO flag NOT set - same as JNE;
JNE - jump if AX is NOT EQUAL to BX;


SIGNED COMPARISONS:
----------------------

JG - jump if AX was GREATER than BX;
JGE - jump if AX was GREATER or EQUAL to BX;
JL - jump if AX was LOWER than BX;
JLE - jump if AX was LOWER or EQUAL to BX;
JNG - jump if AX was NOT GREATER than BX;
JNGE - jump if AX was NOT GREATER or EQUAL to BX;
JNL - jump if AX was NOT LOWER than BX;
JNLE - jump if AX was NOT LOWER or EQUAL to BX;
JZ - jump if ZERO flag set - same as JE;
JE - jump if AX EQUALS BX;
JNZ - jump if ZERO flag NOT set - same as JNE;
JNE - jump if AX is NOT EQUAL to BX;


NOT SO COMMON ONES:
---------------------

JC - jump if CARRY flag set;
JNC - jump if CARRY flag NOT set;
JO - jump if OVERFLOW flag is set;
JNO - jump if OVERFLOW flag NOT set;
JP - jump if PARITY flag is set;
JNP - jump if PARITY flag is NOT set;
JPE - jump if PARITY is EVEN - same as JP;
JPO - jump if PARITY is ODD - same as JNP;
JS - jump if SIGNAL flag is NOT set;
JNS - jump if SIGNAL flag NOT SET.

Phew! My eyes have almost dried out after staring at this screen for so long!


Anyway, here's what they look like:

敖陳陳賃陳陳堕陳賃陳陳堕陳賃陳陳堕陳賃陳陳堕陳朕
Flag SF ZF -- AF -- PF -- CF
団陳陳津陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳調
Bit 07 06 05 04 03 02 01 00
青陳陳珍陳陳祖陳珍陳陳祖陳珍陳陳祖陳珍陳陳祖陳潰

Key:
------

SF - Sign flag;
ZF - Zero flag;
AF - Auxillary flag;
PF - Parity flag.
CF - Carry flag.

Note: THERE ARE MANY MORE FLAGS TO LEARN. They'll be covered in a later
Tutorial.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

THINGS TO DO:

1) Go over the basic Assembler frame and memorise it all.
2) Try writing a simple program that displays some _imaginative_ comments.
3) Learn the least cryptic JUMP statements off by heart.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Okay, last tute I gave you some pretty nifty procedures, and asked you to
comment them. I didn't wnat a detailed explanation of what they did - you're
not expected to know that yet - just a summary of what each instruction does.

EG:

MOV AX, 0003h ; AX now equals 03h;
ADD AX, 0004h ; AX now equals 07h;

So, here's the full set of procedures with comments:


{ This procedure clears the screen in text mode }

Procedure ClearScreen(A : Byte; Ch : Char); Assembler;

Asm { ClearScreen }
mov ax, 0B800h { Move the video address into AX }
mov es, ax { Point ES to the video segment }
xor di, di { Zero out DI }
mov cx, 2000 { Move 2000 (80x25) into CX }
mov ah, A { Move the attribute into AH }
mov al, &Ch { Move the character to use into AL }
rep stosw { Do it }
End; { ClearScreen }


Explanation:

We zero out DI so it equals 0 - the left hand corner of the screen. This
is where we will start filling the screen from.

We move 2000 into CX because we will be putting 2000 characters onto the
screen.


{ This procedure moves the cursor to location X, Y }

Procedure CursorXY(X, Y : Word); Assembler;

Asm { CursorXY }
mov ax, Y { Move Y value into AX }
mov dh, al { Y goes into DH }
dec dh { Adjust for zero based routine }
mov ax, X { Move X value into AX }
mov dl, al { X goes into DL }
dec dl { Adjust for zero based routine }
mov ah, 2 { Call the relevant function }
xor bh, bh { Zero out BH - page 0 }
int 10h { Do it }
End; { CursorXY }


Explanation:

The 'adjusting for the zero-based BIOS' is done because the BIOS refers to
position (1, 1) as (0, 0), and likewise (80, 25) as (79, 24).



Procedure PutPixel(X, Y : Integer; C : Byte; Adr : Word); Assembler;

Asm { PutPixel }
mov ax, [Adr] { Move the address of the VGA into AX }
mov es, ax { Dump AX in ES }
mov bx, [X] { Move X value into BX }
mov dx, [Y] { Move Y value into DX }
xchg dh, dl { From here onwards calculates the }
mov al, [C] { offset of the pixel to be plotted }
mov di, dx { and puts this value in DI. We will }
shr di, 2 { cover this later - next tute - when }
add di, dx { we cover shifts vs muls. }
add di, bx
stosb { Store the byte at ES:DI }
End; { PutPixel }


NOTE: I would be greatly interested in finding a PutPixel procedure faster
than this one. I have seen an inline one which does this in about half
the time, but even so, this one is pretty hot.


{ This procedure is a CPU independant delay function }

Procedure Delay(ms : Word); Assembler;

Asm { Delay }
mov ax, 1000 { Move the # of ms in a sec into AX }
mul ms { Make AX = # of ms to wait }
mov cx, dx { Get ready for delay - put # of ms }
mov dx, ax { where necessary }
mov ah, 86h { Create the delay }
int 15h
End; { Delay }


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Just about all the fluid has left my eyes now - it's nearly midnight - so I'd
better stop. Sorry that the comments are a bit short, but I need my sleep!

Next tutorial will cover:

Shifts - what are they?
Some CMP/JMP examples.
How VGA memory is arranged, and how to access it.
um, some other great topic.

Next week I'll make an effort to show you how to access memory quickly, ie
the VGA, and give you some examples.

If you wish to see a topic discussed in a future tutorial, then mail me, and
I'll see what I can do.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Don't miss out!!! Download next week's tutorial from my homepage at:

http://www.faroc.com.au/~blackcat


See you next week!

- Adam.
"To try is to fail....so to succeed is to be lucky"

#5 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:09 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART IV
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.5
Date : 01-03-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Welcome back, budding Assembler coders. The tutorials seem to be getting
popular now, and I've had some mail requesting me to cover the VGA so I'll
give it a go. This is basically what I've been leading up to in my own
disjointed way anyhow, as graphics programming is not only rewarding, it's
also fun too! Well, I think it's fun. :)

Firstly though, we must finish off the CMP/JMP stuff, and cover shifts. When
you're coding in Assembler, you'll find comparisons, shifts and testing bits
are very common operations.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

A Comparison Example
-----------------------

I won't bother going over the following example - it's fairly easy to
understand and you should get the basic idea anyway.

DOSSEG
.MODEL SMALL
.STACK 200h
.DATA

FirstString DB 13, 10, "Is this a great tutorial or what? :) - $"
SecondString DB 13, 10, "NO? NO? What do you mean, NO?$"
ThirdString DB 13, 10, "Excellent, let's hear you say that again.$"
FourthString DB 13, 10, "Just a Y or N will do.$"
ExitString DB 13, 10, "Fine, be like that!$"

.CODE

START:
MOV AX, @DATA ; New way of saying:
MOV DS, AX ; DS -> SEG data segment

KeepOnGoing:
MOV AH, 9
MOV DX, OFFSET FirstString ; DX -> OFFSET FirstString
INT 21h ; Output the first message

MOV AH, 0 ; Get a key - store it in AX
INT 16h ; AL - ASCII code, AH - scan code
; It doesn't echo onto the screen
; though, we have to do that ourselves

PUSH AX ; Here we display the char - note that
MOV DL, AL ; we save AX. Obviously, using AH to
MOV AH, 2 ; signal to print a string destroys AX
INT 21h
POP AX

CMP AL, "Y" ; Check to see if 'Y' was pressed
JNE HatesTute ; If it was, keep going

MOV AH, 9 ; Display the "Excellent..." message
MOV DX, OFFSET ThirdString
INT 21h
JMP KeepOnGoing ; Go back to the start and begin again

HatesTute:
CMP AL, "N" ; Make sure it was 'N' they pressed
JE DontLikeYou ; Sadly, it was equal

MOV DX, OFFSET FourthString ; Ask the user to try again
MOV AH, 9
INT 21h
JMP KeepOnGoing ; Let 'em try

DontLikeYou:
MOV DX, OFFSET SecondString ; Show the "NO? NO? What..." string
MOV AH, 9
INT 21h

MOV DX, OFFSET ExitString ; Show the "Fine, be like that!" string
MOV AH, 9
INT 21h

MOV AX, 4C00h ; Return to DOS
INT 21h
END START

You should understand this example, play around with it and write something
better. Those with a "Peter Norton's Guide to..." book or similar,
experiment with the keyboard subfunctions, and see what other similar GetKey
combinations exist, or better still, play around with interrupt 10h and
go into some weird video mode - one which your PC supports! - and use some
color.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Shifts
--------

A simple concept, and one which I should have discussed before, but like I
said - I have my own disjointed way of going about things.

First you'll need to understand some hexadecimal and binary arithmetic - a
subject I _should_ have covered before. I usually use a scientific
calculator - hey, I always use a calculator, I'm not stupid! - but it is good
to be able to know how to multiply, add and convert between the various bases.

You also cannot use a calculator in Computing exams, not in Australia anyway.


CONVERTING BINARY TO DECIMAL:

Way back in Tutorial One we looked at what binary numbers look like, so
imagine I have an eight-bit binary number such as:

11001101

What is this in decimal??? There are a number of ways to convert such a
number, and I use the following, which I believe is probably the easiest:

浜様様様様様様様様様様様冤様様冤様用様様冤様用様様冤様用様様冤様様
Binary Number 1 1 0 0 1 1 0 1
把陳陳陳陳陳陳陳陳陳陳陳田陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳陳
7 6 5 4 3 2 1 0
Decimal equivalent 2 2 2 2 2 2 2 2
把陳陳陳陳陳陳陳陳陳陳陳田陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳陳
Decimal equivalent 128 64 32 16 8 4 2 1
把陳陳陳陳陳陳陳陳陳陳陳田陳陳祖陳珍陳陳祖陳珍陳陳祖陳珍陳陳祖陳陳伉陳陳
Decimal value 128 + 64 + 0 + 0 + 8 + 4 + 0 + 1 = 205
藩様様様様様様様様様様様詫様様様様様様様様様様様様様様様様様様様様様様様

Get the idea? Note for the last line, it would be more accurate to write:

1 x 128 + 1 x 64 + 0 x 32 + 0 x 16 + 1 x 8 + 1 x 4 + 0 x 2 + 1 x 1
= 128 + 64 + 0 + 0 + 8 + 4 + 0 + 1
= 205

Sorry if this is a little confusing, but it is difficult to explain without
demonstrating. Here's another example:

浜様様様様様様様様様様様冤様様冤様用様様冤様用様様冤様用様様冤様様
Binary Number 0 1 1 1 1 1 0 0
把陳陳陳陳陳陳陳陳陳陳陳田陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳陳
7 6 5 4 3 2 1 0
Decimal equivalent 2 2 2 2 2 2 2 2
把陳陳陳陳陳陳陳陳陳陳陳田陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳陳
Decimal equivalent 128 64 32 16 8 4 2 1
把陳陳陳陳陳陳陳陳陳陳陳田陳陳祖陳珍陳陳祖陳珍陳陳祖陳珍陳陳祖陳陳伉陳陳
Decimal value 0 + 64 + 32 + 16 + 8 + 4 + 0 + 0 = 124
藩様様様様様様様様様様様詫様様様様様様様様様様様様様様様様様様様様様様様

Note:

You can use this technique on 16 or 32-bit words too, just work your way
up. Eg: After 128, you'd write 256, then 512, 1024 and so on.

You can tell if the decimal equivalent will be odd or even by the first
bit. Eg: In the above example, the first bit = 0, so the number is
EVEN. In the first example, the first bit is 1, so the number is ODD.

FUN FACT: In case you didn't already know, bit stands for Binary digIT. :)


CONVERTING DECIMAL TO BINARY:

This is probably easier than base-2 to base-10. To find out what 321 would be
in binary, you'd do the following:

321 = 256 X 1
321 - 256 = 65 = 128 X 0
65 = 64 X 1
65 - 64 = 1 = 32 X 0
1 = 16 X 0
1 = 8 X 0
1 = 4 X 0
1 = 2 X 0
1 = 1 X 1

And you get the binary number - 101000001. Easy huh? Let's just try another
one to make sure we know how:

198 = 128 X 1
198 - 128 = 70 = 64 X 1
70 - 64 = 6 = 32 X 0
6 = 16 X 0
6 = 8 X 0
6 = 4 X 1
6 - 4 = 2 = 2 X 1
2 - 2 = 0 = 1 X 0

And this gives us - 11000110. Note how you can check the first digit to see
if you got your conversion right. When I wrote the first example, I noticed
I had made a mistake when I checked the first bit. On the first example, I
got 0 - not good for an odd number. I realised my mistake and corrected the
example.


CONVERTING HEXADECIMAL TO DECIMAL:

Before we begin, you should know that the hexadecimal number system uses the
'digits':

0 = 0 (decimal) = 0 (binary)
1 = 1 (decimal) = 1 (binary)
2 = 2 (decimal) = 10 (binary)
3 = 3 (decimal) = 11 (binary)
4 = 4 (decimal) = 100 (binary)
5 = 5 (decimal) = 101 (binary)
6 = 6 (decimal) = 110 (binary)
7 = 7 (decimal) = 111 (binary)
8 = 8 (decimal) = 1000 (binary)
9 = 9 (decimal) = 1001 (binary)
A = 10 (decimal) = 1010 (binary)
B = 11 (decimal) = 1011 (binary)
C = 12 (decimal) = 1100 (binary)
D = 13 (decimal) = 1101 (binary)
E = 14 (decimal) = 1110 (binary)
F = 15 (decimal) = 1111 (binary)

You'll commonly hear hexadecimal referred to as hex, or base-16 and it is
commonly denoted by an 'h' - eg 4C00h, or a '$', eg - $B800.

Working with hexadecimal is not as hard as it may look, and converting back
and forth is pretty easy. As an example, we'll convert B800h to decimal:

FUN FACT: B800h is the starting address of the video in text mode for CGA and
above display adaptors. :)

B = 4096 x B = 4096 x 11 = 45056
8 = 256 x 8 = 256 x 8 = 2048
0 = 16 x 0 = 16 x 0 = 0
0 = 1 x 0 = 1 x 0 = 0

So B800h = 45056 + 2048 + 0 + 0
= 47104

Note: For hexadecimal numbers greater than FFFFh (65535 decimal),
you merely follow the same procedure as for binary, so for
the fifth hexadecimal digit, you'd multiply it by 65535.

Hit 16 X X on your calculator, and keep hitting =. You'll
see the numbers you'd need to use. The same applies for
binary. Eg: 2 X X and = would give you 1, 2, 4, 8, 16...
etc.

Okay, that seemed pretty easy. I don't even think we need a second example.
Let's have a crack at:


CONVERTING DECIMAL TO HEXADECIMAL:

Again, the same sort of procedure as the one we followed for binary. So
convert 32753 to hexadecimal, you'd do:


32753 / 4096 = 7 (decimal) = 7h

32753 - (4096 x 7) = 4081

4081 / 256 = 15 (decimal) = Fh

4081 - (256 x 15) = 241

241 / 16 = 15 (decimal) = Fh

241 - (16 x 15) = 1

1 / 1 = 1 (decimal) = 1h


So eventually we get 7FF1h as our answer. This is not a particularly nice
process and requires some explanation.

1) When you divide 32753 by 4096 you get 7.9963379... We are not interested
in the .9963379 rubbish, we just take the 7, as 7 is the highest whole
number that we can use.

2) The remainder left over from the above operation is 4081. We must now
perform the same operation on this, except with 256. Dividing 4081
by 256 gives us 15.941406... Again, we just take the 15.

3) Now we have a remainder of 241. Dividing this by 16 gives us 15.0625.
We take the 15, and calculate the remainder.

4) Our last remainder just happens to be one. Dividing this by one gives
us, you guessed it - one. YOU SHOULD NOT GET AN ANSWER TO SEVERAL
DECIMAL PLACES HERE. IF YOU HAVE - YOU HAVE DONE THE CALCULATION WRONG.

It's a particularly nasty process, but it works. I do not use this except
when I have to - I'm not crazy - I use a scientific calculator, or Windows
Calculator <shudder> if I must.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Okay, now we've dealt with the gruesome calculations, you're ready for
shifts. There are generally two forms of the shift instruction - SHL (shift
left) and SHR (shift right). Basically, all these instructions do is shift
and expression to the left or right by a number of bits. Their main
advantage is their ability to let you replace slow multiplications with much
faster shifts. You will find this will speed up pixel/line/circle algorithms
by an amazing amount.

PC's are becoming faster and faster by the day - a little too fast for my
liking. Back in the days of the XT - multiplication was _really_ slow -
perhaps taking up to four seconds for certain operations. Not so much of this
applies today, but it is still a good idea to optimize your code.

When we plot a pixel onto the screen, we have to find the offset for the pixel
to plot. Basically, what we do is to multiply the Y location by 320, add the
X location onto it, and add this to address A000h.

So basically, we get: A000:Yx320+X

Now, as fast as your wonderful 486 or Pentium machine is, this could be made
a lot faster. Lets rewrite that equation above so we use some different
numbers:

8 6
Offset = Y x 2 + Y x 2 + X
Or:
Offset = Y x 256 + y x 64 + X

Recognise those numbers? They look an awful lot like the ones we saw in that
binary-to-decimal conversion table. However, we are still using
multiplication. How can we incorporate shifts into the picture?

What about:

Offset = Y SHL 8 + Y SHL 6 + X

Now this is a _lot_ faster, as all the computer has to do is shift the number
left - much better. Note that shifting to the left INCREASES the number,
and shifting to the right will DECREASE the number.

Here's an example that may help you if you are still unsure as to what is
going on. Let's say that we're working in base-10 - decimal. Now let's take
the number 36 as an example. Shifting this number LEFT by 1, gives us:

36 + 36 = 72

Now SHL 2:

36 + 36 + 36 + 36 = 144

And SHL 3:
36 + 36 + 36 + 36 + 36 + 36 + 36 + 36 = 288

Notice the numbers forming? There were 2 36's with SHL 1, 4 36's with SHL 2
and 8 36's with SHL 3. Following this pattern, it would be fair to assume
that 36 SHL 4 will equal 36 x 16.

Note however, what is really happening. If you were to work out the binary
value of 36, which looks like this: 100100, and then shifted 36 LEFT by two,
you'd get 144, or 10010000. All the CPU actually does it stick a few extra
1's and 0's in a location in memory.


As another example, take the binary number 1000101. If we were to shift it
LEFT 3, we'd end up with:

1 0 0 0 1 0 1
<---------- SHL 3
1 0 0 0 1 0 1 0 0 0

Now lets shift the number 45 RIGHT 2. In binary this is 101101. Hence:

1 0 1 1 0 1
SHR 2 ---->
1 0 1 1

Notice what has occurred? It is much easier for the CPU to just move some
bits around, (approximately 2 clock ticks), rather than to multiply a number
out. (Can get to around 133 clock ticks).

We will be using shifts a lot when programming the VGA, so make sure you
understand the concepts behind them.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

PROGRAMMING THE VGA IN ASSEMBLER

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


I have received quite a bit of mail asking me to cover the VGA. So for all
those who asked, we'll be spending most of our time, but not all, on
programming the VGA. After all, doesn't everyone want to code graphics?

When we talk about programming the VGA, we are generally talking about mode
13h, or one of its tweaked relatives. In standard VGA this is the _only_ way
to use 256 colors, and it's probably one of the easiest modes to use too.
If you've ever tried experimenting with SVGA, you'll understand the nightmare
it is for the programmer in supporting all the different SVGA cards that
exist - except if you use VESA that is, which we'll discuss another time. The
great thing about standard mode 13h is you know that just about every VGA card
in existence will support it. People today often ignore mode 13h, thinking
the resolution to be too grainy by today's standards, but don't forget that
Duke Nukem, DOOM, DOOM II, Halloween Harry and most of the Apogee games use
this mode to achieve some great effects.

The great thing about mode 13h - that's 320x200x256 in case you were unaware,
is that accessing VGA RAM is incredibly easy. As 320 x 200 only equals
64,000, it is quite possible to fit the entire screen into one 64K segment -
leaving out the hell of planes, (or should that be plains of Hell?), and
masking registers.

The bad news is that standard mode 13h really only gives you one page to use,
seriously hampering scrolling and page-flipping. We'll later cover how to
get into your own modes - and mode X which will avoid these problems.


So, how do you get into the standard mode 13h?

The answer is simple. We use interrupt 10h - video interrupt, and call
subfunction 00h - set mode. In Pascal, you could declare a procedure like
this:

Procedure Init300x200; Assembler;

Asm { Init300x200 }
mov ah, 00h { Set video mode }
mov al, 13h { Use mode 13h }
int 10h { Do it }
End; { Init300x200 }


You may also see:

mov ax, 13h
int 10h

This is perfectly correct, and probably saves one clock tick by not putting
00h in AH and then 13h in AL, but it is more correct to use the first
example.


Okay, so we're in mode 13h, but what can we actually do in it, other than look
at a blank screen? We could go back to text mode by using:

mov ah, 00h
mov al, 03h
int 10h

but that's a little dull. Why not plot a pixel?


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

There are a number of ways you could get a pixel on the screen. The easiest
way in Assembler is to use interrupts. You do it like this in Pascal:

Procedure PutPixel(X, Y : Integer; Color : Byte); Assembler;

Asm { PutPixel }
mov ah, 0Ch { Draw pixel subfunction }
mov al, [Color] { Move the color to plot in AL }
mov cx, [X] { Move the X value into CX }
mov dx, [Y] { Move the Y value into DX }
mov bx, 1h { BX = 1, page 1 }
int 10h { Plot it }
End; { PutPixel }


However, even though this is in Assembler, it isn't particularly speedy. Why
you ask? Because it uses interrupts. Interrupts are fine for getting in and
out of video modes, turning the cursor on and off, etc... but not for
graphics.

You can think of interrupts like an answering machine. "The CPU is busy right
now, but if you leave your subfunction after the tone - we'll get back to
you."

Not good. Let's use that technique we discussed earlier during shifts. What
we want to do is put the value of the color we want to plot into the VGA
directly. To do this, we'll need to move the address of the VGA into ES,
and calculate the offset of the pixel we want to plot. An example of this
is shown below:

Procedure PutPixel(X, Y : Integer; Color : Byte); Assembler;

Asm { PutPixel }
mov ax, 0A000h { Move the segment of the VGA into AX, }
mov es, ax { and now into ES }
mov bx, [X] { Move the X value into BX }
mov dx, [Y] { Move the Y value into DX }
mov di, bx { Move X into DI }
mov bx, dx { Move Y into BX }
shl dx, 8 { In this part we use shifts to multiply }
shl bx, 6 { Y by 320 }
add dx, bx { Now here we add X onto the above, }
add di, dx { giving us DI = Y x 320 + X }
mov al, [Color] { Put the color to plot into AL }
stosb { Put the byte, AL, at ES:DI }
End; { PutPixel }

This procedure is fast enough to begin with, though I gave out a much faster
one a few tutorials ago which uses a pretty ingenious technique to get DI.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Okay, I think that's enough for this week. Have a play with the PutPixel
routines and see what you can do with them. For those with a "Peter Norton's
Guide to..." book, see what other procedures you can make using interrupts.


THINGS TO DO:

1) We covered a lot in this tutorial, and some important concepts were
in it. Make sure you are comfortable with the comparisons, because
we'll get into testing bits soon.

2) Make sure you understand the binary -> decimal, decimal -> binary,
decimal -> hex and hex -> decimal stuff. Make yourself some example
sums and test your answers with Windows Calculator.

3) You _must_ understand shifts. If you are still having problems,
make some expressions up on paper, and test your answers with a program
such as:

Begin { Main }
WriteLn(45 SHL 6);
ReadLn;
End. { Main }

and/or Windows Calculator.

4) Have a look at the VGA stuff, and make sure you have grasped the theory
behind it, because next week we're really going to go into it in
depth.

Next week I'll also try to give some C/C++ examples as well as the Pascal ones
for all you C programmers out there.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Next tutorial will cover:

How the VGA is arranged
How we can draw lines and circles
Getting and setting the palette in Assembler
Fades
Some C/C++ examples

If you wish to see a topic discussed in a future tutorial, then mail me, and
I'll see what I can do.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Don't miss out!!! Download next week's tutorial from my homepage at:

http://www.faroc.com.au/~blackcat


See you next week!

- Adam.

" I _never_ write code with bugs, I just add some unintentional features! "
"To try is to fail....so to succeed is to be lucky"

#6 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:11 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART V
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.5
Date : 15-03-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Well, another week or so seems to have gone by... Another week I should have
been using to accomplish something useful. Anyway, it seems that the
tutorials have gained a bit more popularity, which is good.

I've also received some demo code from someone who seems to have found the
tutorials of some use. Please, if you attempt something either with the help
of the tutorials or on your own, please send it to me. I like to see what
people have made of my work, or just how creative you all are. If you write
something that I think could be useful for others to learn from, or is just
pretty cool, I'll stick it up on my web site.

Note that I included a starfield demonstration in this week's tutorial just
for the hell of it. You can run STARS.EXE, or look at STARS.PAS for the full
source. It's only a simple demo, but it can be used to achieve some very
nice effects.

Now, this week we're firstly going to list a summary of all the instructions
that you should have learnt by now, and a few new ones as well. Then we'll
take a look at how the VGA is arranged, and cover a simple line routine.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

THE INSTRUCTION SET SUMMARY

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰



ADC <DEST>, <SOURCE> - Name: Add with Carry
Type: 8086+

Description: This instruction adds <SOURCE>
to <DEST> and adds the value stored in the
carry flag, which will be a one or a zero
to <DEST> also.

Basically, DEST = DEST + SOURCE + CF

EG: ADC AX, BX


ADD <DEST>, <SOURCE> - Name: Add
Type: 8086+

Description: This instruction adds <SOURCE>
and <DEST>, storing the result in <DEST>.

EG: ADD AX, BX


AND <DEST>, <SOURCE> - Name: Boolean AND
Type: 8086+

Description: This instruction performs a
bit by bit comparison of <DEST> and
<SOURCE>, storing the result in <DEST>.

EG: AND 0, 0 = 0
AND 0, 1 = 0
AND 1, 0 = 0
AND 1, 1 = 1


BT <DEST>, <BIT NUMBER> - Name: Bit Test
Type: 80386+

Description: This instruction tests
<BIT NUMBER> of <DEST> which can either
be a 16 or 32-bit register or memory
location. If <DEST> is a 16-bit number
then <BIT NUMBER> can range from 0 - 15,
else if <DEST> is a 32-bit number, then
<BIT NUMBER> may have a value from 0 to 31.

The value held in <BIT NUMBER> of <DEST> is
then copied into the carry flag.

EG: BT AX, 3
JC WasEqualToOne


CALL <DEST> - Name: Procedure Call
Type: 8086+

Description: This instruction simply calls
a subroutine. In more technical terms, it
pushes the address of the next instruction,
IP, onto the stack, and then sets the
instruction pointer, IP, to the value
specified by <DEST>.

EG: CALL MyProc


CBW - Name: Convert Byte to Word
Type: 8086+

Description: This instruction extends the
byte in AL to AX.

EG: MOV AL, 01h
CBW
ADD BX, AX ; Do something with AX


CLC - Name: Clear Carry Flag
Type: 8086+

Description: This instruction clears the
carry flag in the flags register to 0.

EG: CLC


CLD - Name: Clear Direction Flag
Type: 8086+

Description: This instruction clears the
direction flag in the flags register to
0. When the direction flag is 0, any
string instructions increment the index
registers SI and DI.

EG: CLD


CLI - Name: Clear Interrupt Flag
Type: 8086+

Description: This instruction clears the
interrupt flag in the flags register to
0, thus disabling hardware interrupts.

EG: CLI


CMC - Name: Complement the Carry Flag
Type: 8086+

Description: This instruction checks the
value currently held in the carry flag.
If it is 0 - it becomes a 1 and if it is
1 - it becomes a 0.

EG: BT AX, 1 ; Test bit 1 of AX
JC WasOne
JMP Done

WasOne:
CMC ; Return CF to 0

Done:


CMP <VALUE1>, <VALUE2> - Name: Compare Integer
Type: 8086+

Description: This instruction compares
<VALUE1> and <VALUE2> and reflects the
comparison in the flags.

EG: CMP AX, BX

See also the Jcc instructions.


CWD - Name: Convert Word to Doubleword
Type: 8086+

Description: This instruction extends the
word in AX to the DX:AX pair.

EG: CWD


DEC <VALUE> - Name: Decrement
Type: 8086+

Description: This instruction subtracts
one from the value held in <VALUE> and
stores the result in <VALUE>.

EG: DEC AX


DIV <VALUE> - Name: Unsigned Division
Type: 8086+

Description: This instruction divides
<VALUE> by either AX for a byte, DX:AX for
a word or EDX:EAX for a doubleword.

For a byte, the quotient is returned in
AL and the remainder in AH, for a word the
quotient is returned in AX and the
remainder in DX and for a DWORD, the
quotient is returned in EAX and the
remainder in EDX.

EG: MOV AX, 12
MOV BH, 5
DIV BH
MOV Quotient, AL
MOV Remainder, AH


IN <ACCUMULATOR>, <PORT> - Name: Input from I/O port
Type: 8086+

Description: This instruction reads a
value from one of the 65536 hardware ports
into the specified accumulator.

AX and AL are commonly used for input
ports, and DX is commonly used to
identify the port.

EG: IN AX, 72h

MOV DX, 3C7h
IN AL, DX


INC <VALUE> - Name: Increment
Type: 8086+

Description: This instruction adds one to
the number held in <VALUE>, and stores
the result in <VALUE>.

EG: MOV AX, 13h ; AX = 13h
INC AX ; AX = 14h


INT <INTERRUPT> - Name: Generate an Interrupt
Type: 8086+

Description: This instruction saves the
current flags and instruction pointer on
the stack, and then calls <INTERRUPT>
based on the value in AH.

EG: MOV AH, 00h ; Set video mode
MOV AL, 13h ; Video mode 13h
INT 10h ; Generate interrupt


Jcc - Name: Jump if Condition
Type: 8086+

I'm not going to repeat myself for all 32 of them, just look in Tutorial
Three for the entire list of them. Bear in mind that it would be a good
idea to call CMP, OR, DEC or something similar before you use one of these
instructions. :)

EG: DEC AX
JZ AX_Has_Reached_Zero


JMP <DEST> - Name: Jump
Type: 8086+

Description: This instruction simply
loads a new value, <DEST>, into the
instruction pointer, thus transferring
control to another part of the code.

EG: JMP MyLabel


LAHF - Name: Load AH with Flags
Type: 8086+

Description: This instruction copies the
low bytes of the flags register into AH.
The contents of AH will look something
like the following after the instruction
has been executed:


敖陳陳賃陳陳堕陳賃陳陳堕陳賃陳陳堕陳賃陳陳堕陳朕
Flag SF ZF -- AF -- PF -- CF
団陳陳津陳陳田陳津陳陳田陳津陳陳田陳津陳陳田陳調
Bit 07 06 05 04 03 02 01 00
青陳陳珍陳陳祖陳珍陳陳祖陳珍陳陳祖陳珍陳陳祖陳潰

You may now test the bits individually, or perform an
instruction similar to the follow to get an individual flag:

EG: LAHF
SHR AH, 6
AND AH, 1 ; AH now contains the ZF flag.


LEA <DEST>, <SOURCE> - Name: Load Effective Address
Type: 8086+

Description: This instruction loads the
memory address that <SOURCE> resides in,
into <DEST>.

EG: I use LEA SI, Str in a procedure
of mine which puts a string on the
screen very fast.


LOOP <LABEL> - Name: Decrement CX and Branch
Type: 8086+

Description: This instruction is a form
of the For...Do loop that exists in most
high-level languages. Basically it loops
back to a label, or memory offset, until
CX = 0.

EG: MOV CX, 12

DoSomeStuff:
;...
;...
;... This will be repeated 12 times

LOOP DoSomeStuff


Lseg <DEST>, <SOURCE> - Name: Load Segment Register
Type: 8086+

Description: This instruction exists in
several forms. All accept the same
syntax, in which <SOURCE> specifies a
48-bit pointer, consisting of a 32-bit
offset and a 16-bit selector. The 32-bit
offset is loaded into <DEST>, and the
selector is loaded into the segment
register specified by seg.

The following forms exist:

LDS
LES
LFS * 32-bit
LGS * 32-bit
LSS

EG: LES SI, A_Pointer


MOV <DEST>, <SOURCE> - Name: Move Data
Type: 8086+

Description: This instruction copies
<SOURCE> into <DEST>.

EG: MOV AX, 3Eh
MOV SI, 12h


MUL <SOURCE> - Name: Unsigned Multiplication
Type: 8086+

Description: This instruction multiplies
<SOURCE> by the accumulator, which depends
on the size of <SOURCE>.

If <SOURCE> is a byte then:

* AL is the multiplicand;
* AX is the product.

If <SOURCE> is a word then:

* AX is the multiplicand;
* DX:AX is the product.

If <SOURCE> is a doubleword then:

* EAX is the multiplicand;
* EDX:EAX is the product.

Note: The flags are left in an un-touched
state except for OF and CF, which are
cleared to 0 if the high byte, word or
dword of the product is 0.

EG: MOV AL, 3
MUL 10
MOV Result, AX


NEG <VALUE> - Name: Negate
Type: 8086+

Description: This instruction subtracts
<VALUE> from 0, resulting in a two's
complement negation of <VALUE>.

EG: MOV AX, 03h
NEG AX ; AX = -3


NOT <VALUE> - Name: Boolean Complement
Type: 8086+

Description: This instruction inverts the
state of each bit in the operand.

EG: NOT CX


OR <DEST>, <SOURCE> - Name: Boolean OR
Type: 8086+

Description: This instruction performs a
boolean OR operation between each bit of
<DEST> and <SOURCE>, storing the result
in <DEST>.

EG: OR 0, 0 = 0
OR 0, 1 = 1
OR 1, 0 = 1
OR 1, 1 = 1


OUT <PORT>, <ACCUMULATOR> - Name: Output to Port
Type: 8086+

Description: This instruction outputs the
value in the accumulator to <PORT>. Using
the DX register to pass the port to OUT,
you may access up to 65,536 ports.

EG: MOV DX, 378h
OUT DX, AX


POP <REGISTER> - Name: Pop Register
Type: 8086+

Description: This instruction pops the
current value off the stack, and places
it into <REGISTER>.

EG: POP AX


POPA - Name: Pop All General Registers
Type: 80186+

Description: This instruction pops all
the 16-bit general purpose registers off
the stack, except for SP.

It is the same as:

POP AX
POP BX
POP CX
...

EG: POPA


POPF - Name: Pop Stack into Flags
Type: 8086+

Description: This instruction pops the
low byte of the flags off the stack.

EG: POPF


PUSH <REGISTER> - Name: Push Register
Type: 8086+

Description: This instruction pushes
<REGISTER> onto the stack.

EG: PUSH AX


PUSHA - Name: Push All General Registers
Type: 80186+

Description: This instruction pushes all
16-bit general purpose registers onto the
stack.

It is the same as:

PUSH AX
PUSH BX
PUSH CX
...

EG: PUSHA


PUSHF - Name: Push Flags
Type: 8086+

Description: This instruction pushes the
low byte of the flags of the stack.

EG: PUSHF


REP - Name: Repeat String Prefix
Type: 8086+

Description: This instruction will repeat
the following instructing for the number
of times specified in the CX register.

EG: MOV CX, 6
REP STOSB ; Store 6 bytes


RET - Name: Near Return from Subroutine
Type: 8086+

Description: This instruction returns IP
to the value it had held before the
last CALL instruction. RET, or RETF for a
far jump, must be called when using
stand alone assembler.

EG: RET


ROL <DEST>, <VALUE> - Name: Rotate Left
Type: 8086+

Description: This instruction rotates
<DEST> <VALUE> times. A rotation is
achieved by shifting <DEST> once, then
transferring the bit shifted off the high
end to the low-order position of <DEST>.

EG: ROL AX, 3


ROR <DEST>, <VALUE> - Name: Rotate Right
Type: 8086+

Description: This instruction rotates
<DEST> <VALUE> times. A rotation is
achieved by shifting <DEST> once, and
transferring the bit shifted off the low
end to the high-order position of <DEST>.

EG: ROR BX, 5


SAHF - Name: Store AH in Flags
Type: 8086+

Description: This instruction loads the
contents of the AH register into bits
7, 6, 4, 2 and 0 of the flags register.

EG: SAHF


SBB <DEST>, <SOURCE> - Name: Subtract with Borrow
Type: 8086+

Description: This instruction subtracts
<SOURCE> from <DEST>, and decrements
<DEST> by one if the carry flag is set,
storing the result in <DEST>.

Basically, <DEST> = <DEST> - <SOURCE> - CF

EG: SBB AX, BX


SHL <DEST>, <VALUE> - Name: Shift Left
Type: 8086+

Description: This instruction shifts <DEST>
left by <VALUE>. I'm not going to go into
the theory behind shifts again. If you
are unsure as to what this instruction
does, please refer to Tutorial Four.

EG: SHL AX, 5


SHR <DEST>, <VALUE> - Name: Shift Right
Type: 8086+

Description: This instruction shifts <DEST>
right by <VALUE>. Please refer to
Tutorial Four for the theory behind shifts.

EG: SHR DX, 1


STC - Name: Set Carry Flag
Type: 8086+

Description: This instruction assigns the
value of the carry flag to one.

EG: STC


STD - Name: Set Direction Flag
Type: 8086+

Description: This instruction sets the
value of the carry flag to one. This
instructs all string operations to
decrement the index registers.

EG: STD
REP STOSB ; DI is being decremented


STI - Name: Set Interrupt Flag
Type: 8086+

Description: This instruction sets the
value of the interrupt flag to one, thus
allowing hardware interrupts to occur.

EG: CLI ; Stop interrupts
... ; Perform crucial function
STI ; Enable interrupts


STOS - Name: Store String
Type: 8086+

Description: This instruction exists in the
following forms:

STOSB - Store a byte - AL
STOSW - Store a word - AX
STOSD - Store a doubleword - EAX

The instructions write the current contents
of the accumulator to the memory location
pointed to by ES:DI. It then increments
or decrements DI according to the operand
used, and the value in the direction flag.

EG: MOV AX, 0A000h
MOV ES, AX
MOV AL, 03h
MOV DI, 0
STOSB ; Store 03 at ES:DI,
; which just happens
; to be at the top of the screen in
; mode 13h


SUB <DEST>, <SOURCE> - Name: Subtract
Type: 8086+

Description: This instruction subtracts
<SOURCE> from <DEST>, storing the result
in <DEST>.

EG: SUB ECX, 12


TEST <DEST>, <SOURCE> - Name: Test Bits
Type: 8086+

Description: This instruction performs a
bit-by-bit AND operation on <SOURCE> and
<DEST>. The result is reflected in the
flags, and they are set as the would be
after an AND operation.

EG: TEST AL, 0Fh ; Check to see if any
; bits set in the low
; nibble of AL


XCHG <VALUE1>, <VALUE2> - Name: Exchange
Type: 8086+

Description: This instruction exchanges the
values in <VLAUE1> and <VALUE2>.

EG: XCHG AX, BX


XOR <DEST>, <SOURCE> - Name: Exclusive Boolean OR
Type: 8086+

Description: This instruction performs a
bit-by-bit exclusive OR operation on
<SOURCE> and <DEST>. The operation is
defined as follows:

XOR 0, 0 = 0
XOR 0, 1 = 1
XOR 1, 0 = 1
XOR 1, 1 = 0

EG: XOR AX, BX


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Phew! What a lot there are, and we only covered the basic ones! You are
not expected to understand each and every one of them though. You probably
saw words like 'Two's Complement', and thought - "What the hell does that
mean?".

Do not worry about them for now. We'll continue at our usual pace, and
introduce the new instructions above one by one, explaining them as we go. If
you already understand them now, then this is an added bonus. You will also
notice that there were a lot of 8086 instructions above. There are actually
very few instances where it is necessary to use a 386 or 486 instruction,
let alone Pentium instructions.

Anyway, before we press on with the VGA, I'll just list the speed at which
each of the above instructions execute at, so you can use this to gauge how
fast your Assembler routines are.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Instruction 386 Clock Ticks 486 Clock Ticks

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

ADC 2 1
ADD 2 1
AND 2 1
BT 3 3
CALL 7+m 3
CBW 3 3
CLC 2 2
CLD 2 2
CLI 5 3
CMC 2 2
CMP 2 1
CWD 2 3
DEC 2 1
DIV - -
- Byte 9-14 13-18
- Word 9-22 13-26
- DWord 9-38 13-42
IN 12/13 14
INC 2 1
INT depends depends
Jcc - -
- Branch 7+m 3
- No Branch 3 1
JMP 7+m 3
LAHF 2 3
LEA 2 1
LOOP 11 6
Lseg 7 6
MOV 2 1
MUL - -
- Byte 9-14 13-18
- Word 9-22 13-26
- DWord 9-38 13-42
NEG 2 1
NOT 2 1
OR 2 1
OUT 10/11 16
POP 4 1
POPA 24 9
POPF 5 9
PUSH 2 1
PUSHA 18 11
PUSHF 4 4
REP depends depends
RET 10+m 5
ROL 3 3
ROR 3 3
SAHF 3 2
SBB 2 1
SHL 3 3
SHR 3 3
STC 2 2
STD 2 2
STI 3 5
STOS 4 5
SUB 2 1
TEST 2 1
XCHG 3 3
XOR 2 1

Note: m = Number of components in next instruction executed.

Ugh, I never want to see another clock-tick again! Now, on with the fun stuff
- the VGA!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

You've probably noticed by now that your video card has more than 256K of RAM.
(If you haven't, then these tutorials are probably not for you.) Even if
you have only 256K of RAM, like my old 386, you'll still be able to get
into mode 13h - 320x200x256. However, this raises some questions.

Multiply 320 by 200 and you'll notice that you only need 64,000 bytes of
memory to store a single screen. (The VGA actually gives us 64K, which is
65,536 bytes for the unaware.) What happened to the remaining 192K or so?

Well, the VGA is actually arranged in bitplanes, like this:


敖陳陳陳3陳陳陳朕
敖祖陳陳2陳陳陳朕
敖祖陳陳1陳陳陳朕
敖祖陳陳0陳陳陳朕


64,000 団


青陳陳陳陳陳陳陳潰

Each plane being 64,000 bytes long. Here's how it works:

A pixel at 0, 0 is mapped in plane 0 at offset 0;
A pixel at 1, 0 is mapped in plane 1 at offset 0;
A pixel at 2, 0 is mapped in plane 2 at offset 0;
A pixel at 3, 0 is mapped in plane 3 at offset 0;
A pixel at 4, 0 is mapped in plane 0 at offset 1 ... and so on ...

Because of the pixels being chained across all four planes, it is impossible
to use multiple pages in mode 13h without having to resort to using a
virtual screen, or something similar.

The automatic mapping of the pixels is handled completely by the video card,
so you can blindly work away without even knowing about the four bitplanes if
you wish.

We'll go onto how we can get around this, by entering a special display mode,
known as Mode X, later, but for now, let's just see what we can do in plain
old mode 13h.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

DRAWING LINES

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


We've gone a little over the size that I'd planned to go to for this tutorial,
and I had intended to cover Bresenham's Line Algorithm, but that'll have to
wait till next week. However, I will cover how to draw a simple horizontal
line in Assembler.


An Assembler Horizontal Line Routine:
---------------------------------------

First we'll need to point ES to the VGA.

This should do the trick:

MOV AX, 0A000h
MOV ES, AX

Now, we'll need to read the X1, X2 and Y values into registers, so something
like this should work:

MOV AX, X1 ; AX now equals the X1 value
MOV BX, Y ; BX now equals the Y value
MOV CX, X2 ; CX now equals the X2 value

It will be necessary to work out how long the line is, so we'll use CX to
store this, seeing as: i) CX already holds the X2 value, and ii) we'll be
using a REP instruction, which will use CX as a counter.

SUB CX, AX ; CX = X2 - X1

Now we'll need to work out what DI will be for the very first pixel we'll be
plotting, so we'll use what we did in the PutPixel routine:

MOV DI, AX ; DI = X1
MOV DX, BX ; DX = Y
SHL BX, 8 ; Shift Y left 8
SHL DX, 6 ; Shift Y left 6
ADD DX, BX ; DX = Y SHL 8 + Y SHL 6
ADD DI, DX ; DI = Y x 320 + X

We have the offset of the first pixel now, so all we have to do is put the
color we want to draw in, in AL, and use STOSB to plot the rest of the line.

MOV AL, Color ; Move the color to plot with into AL
REP STOSB ; Plot CX pixels

Note that we used STOSB because it will increment DI for us, thus saving
a lot of MOV's and INC's. Now, depending on what language you'll use to
implement this in, you'll get something like:


void Draw_Horizontal_Line(int x1, int x2, int y, char color);
{
asm {
mov ax, 0xa000
mov es, ax ; Point ES to the VGA

mov ax, x1 ; AX = X1
mov bx, y ; BX = Y
mov cx, x2 ; CX = X2

sub cx, ax ; CX = Difference of X2 and X1

mov di, ax ; DI = X1
mov dx, bx ; DX = Y
shl bx, 8 ; Y SHL 8
shl dx, 6 ; Y SHL 6
add dx, bx ; DX = Y SHL 8 + Y SHL 6
add di, dx ; DI = Offset of first pixel

mov al, color ; Put the color to plot in AL
rep stosb ; Draw the line
}
}


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

We'll now we've covered how to draw a simple horizontal line. The above
routine isn't blindingly fast, but it isn't all that bad either. Just
changing the calculation of DI part to look like the fast PutPixel I gave out
in Tutorial Two would probably double the speed of this routine.

My own horizontal line routine is probably about 4 to 5 times as fast as this
one, so in the future, I'll show you how to optimize this one fully. Next
week we'll also cover how to get and set the palette, and how we can draw
circles. I'm sorry it didn't make it into this tutorial, but this one sort of
grew a bit...


THINGS TO DO:
---------------

1) Write a vertical line routine based on the one above. Clue: You'll
need to increment DI by 320 at some stage.

2) Go over the list of Assembler instructions, and learn as many as you
can.

3) Have a look at the Starfield I wrote, and try to fix the bugs in it.
See what you can do with it.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Sorry again that I didn't include the things I said I would last week, but
as I said, the tutorial just grew, and I'm a bit behind with some other
projects I'm supposed to be working on.


Next week's tutorial _will_ include:

Line algorithms and examples;
A circle algorithm;
The palette;
Something else that you ought to know...


If you wish to see a topic discussed in a future tutorial, then mail me, and
I'll see what I can do.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Don't miss out!!! Download next week's tutorial from my homepage at:

http://www.faroc.com.au/~blackcat


See you next week!

- Adam.
"To try is to fail....so to succeed is to be lucky"

#7 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:13 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART VI
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.3
Date : 13-04-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Hello again, Assembler coders. This edition is a little late, but I had
a lot of other things to finish, and I'm working on a game of my own now.
It's a strategy game, like Warlords II, and I think I'm going to have to
write most of the code for it in 640x480, not my beloved 320x200 - but I may
change my mind. Heck, the amount of games I started writing but never got
around to finishing is pretty large, so this one may not get all that far.

Anyway, I said we'd be having a look at some line/circle routines this week,
so here we go...


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Last week we came up with the following horizontal line routine -

mov ax, 0A000h
mov es, ax ; Point ES to the VGA

mov ax, X1
mov bx, Y ; BX = Y
mov cx, X2 ; CX = X2

sub cx, ax ; CX = Difference of X2 and X1

mov di, ax ; DI = X1
mov dx, bx ; DX = Y
shl bx, 8 ; Y SHL 8
shl dx, 6 ; Y SHL 6
add dx, bx ; DX = Y SHL 8 + Y SHL 6
add di, dx ; DI = Offset of first pixel

mov al, color ; Put the color to plot in AL
rep stosb ; Draw the line


Now although this routine was much faster than the BGI routines, (or whatever
your compiler provides), it could be improved upon greatly. If we go through
the routine with the list of clock ticks provided in the last tutorial, you'll
see that it chews up quite a few.

I'll leave optimization up to you for now, (we'll get onto that in a later
tutorial), but either replacing the STOSB with MOV ES:[DI], AL or STOSW will
speed things up quite a bit. Don't forget that if you decide to use a loop,
to whack words onto the VGA, you will have to decrement CX by one.

Now, lets get on to a vertical line. We'll have to calculate the offset of
the first pixel as we did with the horizontal line routine, so something like
this should do:


mov ax, 0A000h ; Put the VGA segment into AX
mov es, ax ; Point ES to the VGA

mov ax, Y1 ; Move the first Y value into AX
shl ax, 6 ; Y x 2 to the power 6
mov di, ax ; Move the new Y value into DI
shl ax, 2 ; Now we have Y = Y x 320
add di, ax ; Add that value onto DI
add di, X ; Add the X value onto DI


Now a bit of basic housekeeping...


mov cx, Y2 ; Store Y2 in CX
mov al, Color ; Store the color to plot with in AL
sub cx, Y1 ; CX = vertical length of line


And now the final loop...


Plot:
mov es:[di], al ; Put the a pixel at the current offset
add di, 320 ; Move down to the next row
dec cx ; Decrement CX by one
jnz Plot ; If CX <> 0, then keep on plotting


Not a fantastic routine, but it's pretty good all the same. Note how it was
possible to perform a comparison after DEC CX. This is an extremely useful
concept, so don't forget that it is possible.

Have a play around with the code, and try and speed it up. Try other methods
of finding the offset, or different methods of flow control.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Now, that was the easy stuff. We are now going to have a look at a line
routine capable of drawing diagonal lines.

The following routine was picked up from SWAG, author unknown, and is an
ideal routine to demonstrate a line algorithm. It is in great need of
optimization, so this can be a task for you - if you wish. Some of the
points needing addressing are:

1) Whoever wrote it had never heard of XCHG - this would save quite a
few clock ticks;

2) It makes one of the great sins of unoptimized code - it will say, move
a value to AX, and then perform an operation involving AX in the next
instruction, thus incurring a penalty cycle. (We'll talk about this
next week).

3) It works with BYTES not WORDS, so the speed of writing to the VGA could
be doubled if words were used.

4) And the biggest sin of all, it uses a MUL to find the offset. Try using
shifts or an exchange to speed things up.

Anyway, I put the comments in, and I feel that it is fairly self explanatory
as it is, so I won't go over how it works. You should be able to pick that
up for yourself. Work through the routine, and see how the gradient for the
line is worked out.


Procedure Line(X1, Y1, X2, Y2 : Word; Color : Byte); Assembler;

Var
DeX : Integer;
DeY : Integer;
IncF : Integer;

Asm { Line }
mov ax, [X2] { Move X2 into AX }
sub ax, [X1] { Get the horiz length of the line - X2 - X1 }
jnc @Dont1 { Did X2 - X1 yield a negative result? }
neg ax { Yes, so make the horiz length positive }

@Dont1:
mov [DeX], ax { Now, move the horiz length of line into DeX }
mov ax, [Y2] { Move Y2 into AX }
sub ax, [Y1] { Subtract Y1 from Y2, giving the vert length }
jnc @Dont2 { Was it negative? }
neg ax { Yes, so make it positive }

@Dont2:
mov [DeY], ax { Move the vert length into DeY }
cmp ax, [DeX] { Compare the vert length to horiz length }
jbe @OtherLine { If vert was <= horiz length then jump }

mov ax, [Y1] { Move Y1 into AX }
cmp ax, [Y2] { Compare Y1 to Y2 }
jbe @DontSwap1 { If Y1 <= Y2 then jump, else... }
mov bx, [Y2] { Put Y2 in BX }
mov [Y1], bx { Put Y2 in Y1 }
mov [Y2], ax { Move Y1 into Y2 }
{ So after all that..... }
{ Y1 = Y2 and Y2 = Y1 }

mov ax, [X1] { Put X1 into AX }
mov bx, [X2] { Put X2 into BX }
mov [X1], bx { Put X2 into X1 }
mov [X2], ax { Put X1 into X2 }

@DontSwap1:
mov [IncF], 1 { Put 1 in IncF, ie, plot another pixel }
mov ax, [X1] { Put X1 into AX }
cmp ax, [X2] { Compare X1 with X2 }
jbe @SkipNegate1 { If X1 <= X2 then jump, else... }
neg [IncF] { Negate IncF }

@SkipNegate1:
mov ax, [Y1] { Move Y1 into AX }
mov bx, 320 { Move 320 into BX }
mul bx { Multiply 320 by Y1 }
mov di, ax { Put the result into DI }
add di, [X1] { Add X1 to DI, and tada - offset in DI }
mov bx, [DeY] { Put DeY in BX }
mov cx, bx { Put DeY in CX }
mov ax, 0A000h { Put the segment to plot in, in AX }
mov es, ax { ES points to the VGA }
mov dl, [Color] { Put the color to use in DL }
mov si, [DeX] { Point SI to DeX }

@DrawLoop1:
mov es:[di], dl { Put the color to plot with, DL, at ES:DI }
add di, 320 { Add 320 to DI, ie, next line down }
sub bx, si { Subtract DeX from BX, DeY }
jnc @GoOn1 { Did it yield a negative result? }
add bx, [DeY] { Yes, so add DeY to BX }
add di, [IncF] { Add the amount to increment by to DI }

@GoOn1:
loop @DrawLoop1 { No negative result, so plot another pixel }
jmp @ExitLine { We're all done, so outta here! }

@OtherLine:
mov ax, [X1] { Move X1 into AX }
cmp ax, [X2] { Compare X1 to X2 }
jbe @DontSwap2 { Was X1 <= X2 ? }
mov bx, [X2] { No, so move X2 into BX }
mov [X1], bx { Move X2 into X1 }
mov [X2], ax { Move X1 into X2 }
mov ax, [Y1] { Move Y1 into AX }
mov bx, [Y2] { Move Y2 into BX }
mov [Y1], bx { Move Y2 into Y1 }
mov [Y2], ax { Move Y1 into Y2 }

@DontSwap2:
mov [IncF], 320 { Move 320 into IncF, ie, next pixel is on next row }
mov ax, [Y1] { Move Y1 into AX }
cmp ax, [Y2] { Compare Y1 to Y2 }
jbe @SkipNegate2 { Was Y1 <= Y2 ? }
neg [IncF] { No, so negate IncF }

@SkipNegate2:
mov ax, [Y1] { Move Y1 into AX }
mov bx, 320 { Move 320 into BX }
mul bx { Multiply AX by 320 }
mov di, ax { Move the result into DI }
add di, [X1] { Add X1 to DI, giving us the offset }
mov bx, [DeX] { Move DeX into BX }
mov cx, bx { Move BX into CX }
mov ax, 0A000h { Move the address of the VGA into AX }
mov es, ax { Point ES to the VGA }
mov dl, [Color] { Move the color to plot with in DL }
mov si, [DeY] { Move DeY into SI }

@DrawLoop2:
mov es:[di], dl { Put the byte in DL at ES:DI }
inc di { Increment DI by one, the next pixel }
sub bx, si { Subtract SI from BX }
jnc @GoOn2 { Did it yield a negative result? }
add bx, [DeX] { Yes, so add DeX to BX }
add di, [IncF] { Add IncF to DI }

@GoOn2:
loop @DrawLoop2 { Keep on plottin' }

@ExitLine:
{ All done! }
End;


I don't think I made any mistakes with the commenting, but I am pretty tired,
and I haven't handy any caffeine for days - let alone hours, so if you spot a
mistake - please let me know.

I was going to include a Circle algorithm, but I couldn't get mine to work
in Assembler - all the floating point math might have something to do with
it. I could include one written in a high level language, but this is meant
to be an Assembler tutorial, not a graphics one. However, if enough people
shout for one...


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

THE INS AND OUTS OF IN AND OUT

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


IN and OUT are a very important part of Assembler coding. They allow you to
directly send/receive data from any of the PC's 65,536 hardware ports, or
registers. The basic syntax is as follows:


IN <ACCUMULATOR>, <PORT> - Name: Input from I/O port
Type: 8086+

Description: This instruction reads a
value from one of the 65536 hardware ports
into the specified accumulator.

AX and AL are commonly used for input
ports, and DX is commonly used to
identify the port.

EG: IN AX, 72h

MOV DX, 3C7h
IN AL, DX


OUT <PORT>, <ACCUMULATOR> - Name: Output to Port
Type: 8086+

Description: This instruction outputs the
value in the accumulator to <PORT>. Using
the DX register to pass the port to OUT,
you may access up to 65,536 ports.

EG: MOV DX, 378h
OUT DX, AX


Okay, that wasn't very helpful, as it didn't tell you much about how to use
it - let alone what to use it for. Well, if you intend to work with the VGA
much, you'll have to be able to program its internal registers. Similar to
the registers that you've been working with up until now, you can think of
changing them just like interrupts, except: 1) You pass the value to the
port, and that's it; and 2) It is pretty near instantaneous.

As an example, we'll cover how to set and get the palette by directly
controlling the VGA's hardware.


Now, the VGA has a lot of registers, but the next three you'd better get to
know pretty well:

03C7h - PEL Address Register (Read)
Sets the palette in read mode

03C8h - PEL Address Register (Write)
Sets the palette in write mode

03C9h - PEL Data Register (Read/Write)
Read in, or write 3 RGB values, every 3rd write, the
index, or color you are setting, is incremented by one.

What all this means is -

If we were to set a color's RGB value, we'd send the value of the color we
wanted to change to 03C8h, then read in 3 values from 03C9h. In Assembler,
we'd do this:

mov dx, 03C8h ; Put the DAC read register in DX
mov al, [Color] ; Put the color's value in AL
out dx, al ; Send AL to port DX
inc dx ; Now use port 03C9h
mov al, [R] ; Put the new RED value in AL
out dx, al ; Send AL to port DX
mov al, [G] ; Put the new GREEN value in AL
out dx, al ; Send AL to port DX
mov al, [B] ; Put the new BLUE value in AL
out dx, al ; Send AL to port DX

And that would do things nicely. To read the palette, we'd do this:

mov dx, 03C7h ; Put the DAC write register in DX
mov al, [Color] ; Put the color's value in AL
out dx, al ; Send AL to port DX
add dx, 2 ; Now use port 03C9h

in al, dx ; Put the value got from port DX in AL
les di, [R] ; Point DI to the R variable - this came from Pascal
stosb ; Store AL in R

in al, dx ; Put the value got from port DX in AL
les di, [G] ; Point DI to the G variable
stosb ; Store AL in G

in al, dx ; Put the value got from port DX in AL
les di, [B] ; Point DI to the B variable
stosb ; Store AL in B

Note how that routine was coded differently. This was originally a Pascal
routine, and as Pascal doesn't like you messing with Pascal variables in
Assembler, you have to improvise.

If you are working in stand alone Assembler, then you can code this much more
efficiently, like the first example. I left the code as it was so those who
are working with a high-level language can get around a particularly annoying
problem.

Now you have seen how useful IN and OUT can be. Directly controlling hardware
is both fast and efficient. In the next few weeks, I may include a list of
some of the most common ports, but if you have a copy of Ralf Brown's
Interrupt List, (available at X2FTP), you will already have a copy.


Note: You can find a link to Ralf's Interrupt List on my homepage.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

A bit more on the FLAGS register:


Now, although we have been using the flags register in almost all our code
up until this point, I haven't really gone into depth about it. You can work
blissfully unaware of the flags, and compare things without knowing what's
really happening, but if you want to get further into Assembler, you'll
need to know some more.

Back in Tutorial Three, I gave an extremely simplistic view of the FLAGS
register. In reality, the FLAGS, or EFLAGS register is actually a 32-bit
register, although only bits 0-18 are used. We really don't need to know any
of the flags above bit 11 for now, but it's good to know they are there.


The EFLAGS register actually looks like this:

18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
AC VM RF -- NT IO/PL OF DF IF TF SF ZF -- AF -- PF -- CF


Now, the flags are as follows:

AC - Alignment Check (80486)
VM - Virtual 8086 Mode
RF - Resume Flag
NT - Nested Task Flag
IOPL - I/O Privilege Level - has a value of 0,1,2 or 3 thus 2 bits big

OF - Overflow Flag
This bit is set to ONE if an arithmetic instruction generated a
result that was too large or too small to fit in the destination
register.

DF - Direction Flag
When set to ZERO, string instructions, such as MOVS, LODS and
STOS will increment the memory address they are working on by one.
This means that say, DI, will be incremented when you use STOSB
to put a pixel at ES:DI. Setting the bit to ZERO will decrement
the memory address after each call.

IF - Interrupt Enable Flag
When this bit is set, the processor will respond to external
hardware interrupts. When the bit is reset, hardware interrupts
are ignored.

TF - Trap Flag
When this bit is set, an interrupt will occur immediately after the
next instruction executes. This is generally used in debugging.

SF - Sign Flag
This bit is changed after arithmetic instructions. The bit
receives the high-order bit of the result, and if set to ONE,
it indicates that the result of the operation was negative.

ZF - Zero Flag
This bit is set when arithmetic instructions generate a result of
zero.

AF - Auxiliary Carry Flag
This bit indicates that a carry out of the low-order nibble of AL
occurred in an arithmetic instruction.

PF - Parity Flag
This bit is set to one when an arithmetic instruction results in
an even number of 1 bits.

CF - Carry Flag
This bit is set when the result of an arithmetic operation is
too large or too small for the destination register or memory
address.


Now, of all those above, you really won't have to worry too much about most
of them. For now, just knowing CF, PF, ZF, SF, IF, DF and OF will be
sufficient. I didn't give the first few comments as they are fairly
technical, and are used mostly in protected mode and complex situations. You
shouldn't have to know them.

You can, if you wish, move a copy of the flags into AH with LAHF - (Load AH
with Flags) - and modify or read individual bits, or change the status of
bits more easily with CLx and STx. However you plan to change the flags,
remember that they can be extremely useful in many situations.

(They can also be very annoying when late at night, lines start drawing
backwards, and you spend an hour wondering why - then remember that you
forgot to clear the direction flag!)


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

I think we've covered quite a few important topics in this tutorial. Brush
up on the flags, and go over the largish line routine, as it is an excellent
example of flow control. Make sure your skills at controlling instruction
flow are perfected.

Next week I'll try to tie all the topics we've covered over the last few
weeks together, and present some form of review of all that you've learnt.
Next week I'll also go into optimization, and how you can speed up all the
code we've worked with so far.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Next week's tutorial will include:

A review of all you've learnt
Optimization
Declaring procedures in Assembler
Linking your code to C/C++ or Pascal

If you wish to see a topic discussed in a future tutorial, then mail me, and
I'll see what I can do.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Don't miss out!!! Download next week's tutorial from my homepage at:

http://www.faroc.com.au/~blackcat


See you next week!

- Adam.
"To try is to fail....so to succeed is to be lucky"

#8 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:15 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART VII
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.3
Date : 01-05-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Hi again, and welcome to the seventh instalment of the Assembler Tutorials.
These tutorials seem to be coming out at an irregular rate, but people are
screaming at me for things I haven't done, and I'm still working on projects
of my own. I hope to spit these tutes out fortnightly.

Now this week we'll be covering two pretty important topics. When I first
began playing around with Assembler I soon realised that Turbo Pascal, (the
language I was working with then), had a few limitations - one of them being
that it was, and still is, a 16-bit language. This meant that if I wanted to
play around with super-fast 32-bit screen writes, I couldn't. Not even with
the built in Assembler, (well, not easily anyway).

What I needed to do was to write code separately in 100% Assembler, then link
it to Turbo. This isn't a particularly hard task, and is one of the things
I'm going to try and teach you today.

The other advantage of writing routines in stand alone Assembler is that you
can also link the resulting object code to another high-level language, like
C.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

WRITING EXTERNAL CODE FOR YOUR HIGH LEVEL LANGUAGE

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Before we begin, you'll need an idea of what far and near calls are. If you
already know, then skip ahead past this little section.

As we discussed before, the PC has a segmented architecture. As you know,
you can only access one 64K segment at a time. Now if you are working on code
less than 64K in size, or in a language that takes care of all your worries
for you, you don't need to worry so much. However, when working in Assembler,
we do.

Imagine we have the following program loaded into memory:



敖陳陳陳陳陳陳陳陳陳陳陳陳

64K 敖陳陳調 ROUTINE TWO 団陳

  青陳陳陳陳陳陳陳陳陳陳陳陳

 敖陳陳陳陳陳陳陳陳陳陳陳陳

青 ROUTINE ONE 団陳陳朕

青陳陳陳陳陳陳陳陳陳陳陳陳
 
64K 敖陳陳陳陳陳陳陳陳陳陳陳陳

Entry 陳調 MAIN PROGRAM
敖田陳
青陳陳陳陳陳陳陳陳陳陳陳田

 Exit


When a JMP is executed to transfer control to Routine One, this will be a
near call. We do not leave the segment that the main body of the program
is located in, and so when the JMP or CALL is executed, and CS:IP is changed
by JMP, only IP need be changed, not CS.

The offset changes, but not the segment.

Now jumping to Routine Two would be different. This leaves the current
segment, and so both parts of the CS:IP pair will need to be altered. This
is a far call.

The problem occurs when the CPU encounters a RET or RETF at the end of the
call. Let's say by accident you put RET at the end of Routine Two instead of
RETF. When the CPU saw RET it would only POP IP off the stack, and so your
machine would probably crash, as CS:IP would now be pointing to garbage.

This point is especially important when linking to a high-level language.
Whenever you write code in Assembler and link it to say, Pascal, remember to
use the {$F+} compiler directive, even if it wasn't a far call. This way,
after Turbo has called the routine, it'll pop both CS and IP, and everything
will be fine.

Failure to do so is at your own risk!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Okay, let's return to the stand alone model we saw in Tutorial Three. I don't
remember rightly, but I think it went something like this:


DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
.CODE

START:


END START


Now, I think it's time you graduated from using that skeleton. Let's look
at other ways we can set up a skeleton routine:


DATA SEGMENT WORD PUBLIC

DATA ENDS


CODE SEGMENT WORD PUBLIC
ASSUME CS:CODE, DS:DATA

CODE ENDS

END

This is an obviously different skeleton. Note how I omitted the period in
front of DATA and CODE. Depending on which assembler/linker you use, you may
need to use a period or you may not. TASM, the assembler I use, supports both
of these formats, so pick one that both you and your assembler are happy with.

Note also the use of DATA SEGMENT WORD PUBLIC. Firstly, WORD tells the
assembler to align the segment on word boundaries.

FUN FACT: You needn't worry about this for now, but Turbo Pascal does this
anyway, so putting BYTE instead of word would make no difference. :)

PUBLIC allows the compiler you use, to access any variables you may wish to
place in the data segment. If you do not want your high-level language to
have access to any variables you may declare, then omit this. If you will
not be needing access to the data segment anyway, then don't bother with the
whole DATA SEGMENT thing.


Now, onto the code segment. Generally, you will need to include this in all
the code you write. :) The assume statement will also be pretty standard in
all you'll work with. You can also expect to see CSEG and DSEG instead of
CODE and DATA. Note that again this is declared public. This is where all
our routines will go.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

So, how do I declare external procedures?


Okay, for this example, we're going to use a few simple routines similar to
those in the MODE13H Pascal library. (Available from my homepage).

If you remember, the procedures looked a bit like this:


Procedure PutPixel(X, Y : Integer; Color : Byte);

Procedure InitMCGA;

Procedure Init80x25;


Fitting these in our skeleton gives us this:


CODE SEGMENT WORD PUBLIC
ASSUME CS:CODE, DS:DATA

PUBLIC PutPixel
PUBLIC InitMCGA
PUBLIC Init80x25

CODE ENDS


END

Now, all we have to do is to code 'em. But hang on a minute - the PutPixel
routine had PARAMETERS. How do we use these in external code???

This is the tricky bit. What we do is push these values onto the stack,
simply saying -- PutPixel(10, 20, 15); -- will do this for us. It's getting
them off that's harder. What I generally do, and I suggest you do, is make
sure that you DECLARE ALL EXTERNAL PROCEDURES FAR. This makes working with
the stack so much easier.


FUN FACT: Remember that what's first on the stack is LAST OFF. :)


When you call PutPixel, the stack will be changed. As this is a far call,
the first four bytes will hold CS:IP. The bytes from then on will hold your
parameters.

To cut a long story short, let's say the stack used to look like this:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...

After calling -- PutPixel(10, 20, 15); -- some time later, it may look like
this:


4C EF 43 12 0F 00 14 00 0A 00 9E F4 3A 23 1E 21 ...

^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^
CS:IP Color Y X Some other crap


Now, to complicate things, the CPU stores words on the stack with THE LEAST
SIGNIFICANT PART FIRST. This doesn't bother us too much, but if you muck
around with a debugger without realising this, you're gonna get really
confused.

Note also, that when Turbo Pascal puts one byte data types on the stack, they
will chew up TWO BYTES, NOT ONE. Don't you just _love_ the way the PC is
organised? ;)


Now, all that I've said up until this point only applies to value parameters -
PARAMETERS YOU CANNOT CHANGE. When you muck around with REFERENCE PARAMETERS,
like -- MyProc(Var A, B, C : Word); -- each parameter now uses FOUR BYTES of
stack space, two for the segment and two for the offset of where the variable
is located in memory.

So if you pushed a variable that was held in, say, memory address 4AD8:000Eh,
no matter what the value of this was, 4AD8:000Eh would be stored on the stack.

As it happens, that would look like 0E 00 D8 4A on the stack, remembering that
the least significant nibble is stored first.


FUN FACT: Value Parameters actually put the value on the stack, Reference
Parameters store the address. :)


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Okay, now I've got you well and truly confused, it gets a little worse!

To reference these parameters in your code, you have to use the stack pointer,
SP. Trouble is, you aren't allowed to play with SP directly, you have to
push BP, and move SP into it. This now adds another two bytes to the stack.
Lets say BP was equal to 0A45h. Before pushing BP, the stack would look like
this:

4C EF 43 12 0F 00 14 00 0A 00

^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^
CS:IP Color Y X

After pushing BP, you get:


45 0A 4C EF 43 12 0F 00 14 00 0A 00

^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^
BP CS:IP Color Y X


Now we've finally got over all that, we can actually access the damn things!
What you'd do after calling -- PutPixel(10, 20, 15); -- to access the Color
value - 15 - is this:

PUSH BP
MOV BP, SP

MOV AX, [BP+6] ; Now we have Color


We can access X and Y like this:

MOV BX, [BP+8] ; Now we have Y

MOV CX, [BP+10] ; Now we have X


And now we restore BP:

POP BP


Now we return from a FAR call, and remove the six bytes of data we put on the
stack:

RETF 6


And that's it!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Now, let's put the PutPixel, InitMCGA and Init80x25 into some Assembler
code. You end up with something like this:


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

CODE SEGMENT WORD PUBLIC
ASSUME CS:CODE, DS:DATA

PUBLIC PutPixel ; Declare the public procedures
PUBLIC InitMCGA
PUBLIC Init80x25

.386 ; Let's use some 386 registers

; 陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

;
; Procedure PutPixel(X, Y : Integer; Color : Byte);
;


PutPixel PROC FAR ; Declare a FAR procedure

PUSH BP
MOV BP, SP ; Set up the stack

MOV BX, [BP+10] ; BX = X
MOV DX, [BP+08] ; DX = Y
XCHG DH, DL ; As Y will always have a value of less than 200,
MOV AL, [BP+06] ; this is 320x200 don't forget, saying XCHG DH,DL
MOV DI, DX ; is an ingenious way of saying SHL DX, 8
SHR DI, 2
ADD DI, DX
ADD DI, BX ; Now we have the offset, so...
MOV FS:[DI], AL ; ...plot it at FS:DI

POP BP
RETF 6

PutPixel ENDP

; 陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

;
; Procedure InitMCGA;
;

InitMCGA PROC FAR

MOV AX, 0A000H ; Point AX to the VGA
MOV FS, AX ; Why not FS?
MOV AH, 00H
MOV AL, 13H
INT 10H
RETF

InitMCGA ENDP

; 陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

;
; Procedure Init80x25;
;

Init80x25 PROC FAR

MOV AH, 00H
MOV AL, 03H
INT 10H
RETF

Init80x25 ENDP

CODE ENDS
END


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

And that's it. I'm sorry if I made the whole thing a bit of a confusing
exercise, but that's the fun of computers! :)

Oh, by the way, you can use the above code in Pascal by assembling it with
TASM, or MASM. <shudder> Next, include it in your code as follows:

{$L WHATEVERYOUCALLEDIT.OBJ}
{$F+}
Procedure PutPixel(X, Y : Integer; Color : Byte); External;
Procedure InitMCGA; External;
Procedure Init80x25; External;
{$F-}

Begin
InitMCGA;
PutPixel(100, 100, 100);
ReadLn;
Init80x25;
End.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

FUNCTIONS AND FURTHER OPTIMIZATION

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


You can get your Assembler routines to return values which you can use in
your high-level language if you wish. The table below contains all the
necessary information you'll need to know:


浜様様様様様様様様用様様様様様様様様様様様
Type to Return Register(s) to Use
把陳陳陳陳陳陳陳陳津陳陳陳陳陳陳陳陳陳陳陳
Byte AL
Word AX
LongInt DX:AX
Pointer DX:AX
Real DX:BX:AX
藩様様様様様様様様溶様様様様様様様様様様様

Now that you've seen how to write external code, you'll probably want to
know how you can tweak it to get the full performance that external code can
deliver.

Some points for you to work with are as follows:

You can't use SP directly, but you CAN use ESP. And no, I don't mean use
your mental powers to get the parameter you want. :)

That'll do away with the slow pushing/popping of BP.

Remember that you'll need to change [xx+6] to [xx+4] for the last,
(first), parameter - as BP is now no longer on the stack.

Have a fiddle, and see what you can do with it. It is possible through
tweaking to make the code faster than the inline routine featured in
MODE13H.ZIP version 1 - (available from my homepage).


Note: I plan to further develop the MODE13H library, adding fonts and other
cool features. It will be eventually coded in standalone Assembler,
AND be callable from C and Pascal.

Standalone code also has a hefty speed increase. Today I tested the
PutPixel routine in the MODE13H library and a standalone PutPixel,
(practically identical), and saw an amazing speed difference.

On a 486SX 25 with 4MB of RAM and a 16-bit VGA card, it took only 5
hundredths of a second for the standalone routine to plot 65,536 pixels
in the middle of the screen, as opposed to 31 hundredths of a second
for the other routine. Big difference, huh?


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

OPTIMIZATION

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


As speedy as Assembler may be, you can always speed things up further. I'm
going to give some coverage on how you can speed your code up on the 80486,
and the 80386, to some extent.

I'm not going to worry too much about the Pentium for now, as the tricks to
use on the Pentium certainly ARE tricky, and would take quite a while to
explain. Also, you should avoid Pentium specific code, (though this is
slowly changing).


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

The AGI (Address Generation Interlock):

What the hell is that?, you ask. An AGI occurs when a register that is
currently being used as a base or index was the destination of a previous
instruction. AGI's are bad, and chew up clock ticks.


EG: MOV ECX, 3
MOV FS, ECX


This can be avoided by performing another instruction between the two MOVes,
as an AGI can only occur on adjacent instructions. (On the 486 anyway.) On
the Pentium, an AGI can occur anywhere between THREE instructions.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Use 32-bit Instructions/Registers:


Using 32-bit registers tends to be faster than using their 16-bit
counterparts. (Particularly EAX, as many instructions actually become one
byte shorter when this register is used. Using DS instead of ES is also
faster for a similar reason.)


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Other things to try:


Avoid LOOPing. Try using just a DEC, or INC following by a JZ or similar
instruction. This can make a big difference.

When zeroing out registers, use XOR rather than MOV xx, 0. Believe it or
not, this is actually faster.

Make use of TEST when you are checking to see if a register is equal to
zero. By ANDing the operands together, no time is wasted in farting
around with a destination register. TEST EAX, EAX is a good way of
checking to see if EAX = 0.

USE SHIFTS! Don't use multiplication to work out even the simplest of
sums. The CPU can move a few ones and zeros left or right much faster
than it can do the multiplication/division.

Make cunning use of LEA. One instruction is all it takes to perform an
integer multiply and store the result in a register. This is a useful
alternative to SHL/SHR. (I know, I know... I said multiplication was
bad. But an LEA can sometimes be useful as it can save several
instructions.)

EG: LEA ECX, [EDX+EDX*4] ; ECX = EDX x 5


Avoid MOVing into segment registers often. If you are going to be
working with a value that doesn't change, such as A000h, then load it
into, say, FS and use FS from then on.

Believe it or not, string instructions, (LODSx, MOVSx, STOSx) are much
faster on a 386 than they are in a 486. If working with 486+, then
use other, more simple instructions instead.

When moving 32-bit chunks, REP STOSD is a lot faster than using a loop
to accomplish the same thing.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Well, now you've seen how you can write external code, declare procedures in
Assembler and optimize your routines. Next week I'm _finally_ going to draw
all that you've learnt together, and see if we can make some sense out of it
all. I'm also going to include a stand alone Assembler example - a better
starfield with palette control, to demonstrate INs and OUTs, program control,
procedures and TESTing.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Next week's tutorial will include:

A review of all you've learnt - finally(sorry!);
Declaring sub-procedures in Assembler;
A nifty example; :)
Some other great topic.

If you wish to see a topic discussed in a future tutorial, then mail me, and
I'll see what I can do.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Don't miss out!!! Download next week's tutorial from my homepage at:

http://www.faroc.com.au/~blackcat


See you next week!

- Adam.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Just a little joke I found on a local BBS, which I thought quite amusing.
I guess those with a UNIX background will understand it more...


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Micro was a real-time operator and dedicated multi-user. His
broad-band protocol made it easy for him to interface with numerous
input/output devices, even if it meant time-sharing.

One evening he arrived home just as the Sun was crashing, and had
parked his Motorola 68040 in the main drive (he had missed the
5100 bus that morning), when he noticed an elegant piece of liveware
admiring the daisy wheels in his garden. He thought to himself,
"She looks user-friendly. I'll see if she'd like an update tonight."

Mini was her name, and she was delightfully engineered with eyes
like COBOL and a PR1ME mainframe architecture that set Micro's
peripherals networking all over the place.

He browsed over to her casually, admiring the power of her twin,
32-bit floating point processors and enquired "How are you, Honeywell?".
"Yes, I am well", she responded, batting her optical fibers engagingly
and smoothing her console over her curvilinear functions.

Micro settled for a straight line approximation. "I'm stand-alone
tonight", he said, "How about computing a vector to my base address?
I'll output a byte to eat, and maybe we could get offset later on."

Mini ran a priority process for 2.6 milliseconds, then transmitted 8 K.
"I've been dumped myself recently, and a new page is just what I need
to refresh my disks. I'll park my machine cycle in your background and
meet you inside." She walked off, leaving Micro admiring her solenoids
and thinking, "Wow, what a global variable, I wonder if she'd like my
firmware?"

They sat down at the process table to top of form feed of fiche and
chips and a bucket of baudot. Mini was in conversation mode and expanded
on ambiguous arguments while Micro gave the occassional acknowledgements,
although, in reality, he was analyzing the shortest and least critical
path to her entry point. He finally settled on the old 'Would you like
to see my benchmark routine', but Mini was again one step ahead.

Suddenly she was up and stripping off her parity bits to reveal the
full functionality of her operating system software. "Let's get BASIC,
you RAM", she said. Micro was loaded by this; his hardware was in
danger of overflowing its output buffer, a hang-up that Micro had
consulted his analyst about. "Core", was all he could say, as she
prepared to log him off.

Micro soon recovered, however, when Mini went down on the DEC and
opened her divide files to reveal her data set ready.
He accessed his fully packed root device and was just about to start
pushing into her CPU stack, when she attempted an escape sequence.

"No, no!", she cried, "You're not shielded!"
"To try is to fail....so to succeed is to be lucky"

#9 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:18 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART VIII
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.4
Date : 28-06-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Well, welcome back assembler coders. This tutorial is _really_ late, and
would have been a lot later were it not for Bjrn Svensson, and many others
like him, who thanks to their determination to get Tutorial 8, persuaded me
to get this thing written. Of, course, this means I've probably failed all
my exams over the past two weeks, but such is life. :)

Okay, this week we're really going to learn something. We're going to take a
much closer look at how we can declare variables, and delve into the world of
structures. You'll learn how to create arrays in Assembler, and this concept
is reinforced with the demo program I included - a fire routine!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

DATA STRUCTURES IN ASSEMBLER

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Okay, by now you should know that you can use the DB, (Declare Byte) and DW,
(Declare Word) to create variables. However, up until now we have been using
them as you would use the Const declaration in Pascal. That is, we have been
using it to assign a byte or word with a value.

EG:

MyByte DB 10 -- which is the same as -- Const MyByte : Byte = 10;


However, we could just have easily said:

MyByte DB ?

...and then later on said:

MOV MyByte, 10


In fact DB is very powerful indeed. Several tutorials ago when you were
learning to put strings on the screen, you saw something along the lines of
this:

MyString DB 10, 13 "This is a string$"


Now the more inquisitive of you would have probably said to yourselves, "Hang
on... that tutorial guy said that DB declares a BYTE. How can DB declare a
string then?" Well, DB has the ability to reserve space for multiple byte
values - from 1 to as many bytes as you need.

You may also have wondered what the 10 and 13 before the text stood for.
Well, dig out your ASCII chart and have a look at character 10 and character
13. You'll notice that 10 is Line Feed and 13 is Carriage Return. Basically,
it's just like saying:

MyString := #10 + #13 + 'This is a string';

in Pascal.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


Okay, so you've seen how to create variables properly. But what about
constants? Well, in Assembler, constants are known as Equates. Equates make
Assembler coding much more easy, and can simplify things greatly. For
instance, if I were to have used the following in previous tutorials:

LF EQU 10
CR EQU 13

DB LF, CR "This is a string$"


...people would have got the 10, 13 thing straight away. However, just to
make things a little more complicated, there is yet another way that you can
assign values to identifiers. You can do things just like you would in BASIC:

Population = 4Ch
Magnitude = 0


Basically, you can bear the following points in mind:

Once you use EQU to assign a value to an identifier, you can not change
it.

EQU can be used to define just about any type - including strings. You
cannot, however, do this when you use a '='. An '=' can only define
numeric values.

You can use EQU almost anywhere in your program.

Values defined with '=' can be changed.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


And now on with one of the trickier aspects of Assembler coding - structures.
Structures are not variables themselves, they are a TYPE - basically a
schematic for a variable.

As an example, if you had the following in Pascal:

Type
Date = Record;
Day : Byte;
Month : Byte;
Year : Word;
End; { Record }

You could represent this in Assembler as follows:

Date STRUC
Day DB ?
Month DB ?
Year DW ?
Date ENDS


However, one of the advantages of Assembler is that you can initialize all or
some of the fields of the structure before you even refer to the structure in
your code segment.

That structure above could easily be written as:

Date STRUC
Day DB ?
Month DB 6
Year DW 1996
Date ENDS


Some important points to remember are as follows:

You can declare a structure anywhere in your code, although for good
program design, you should really put them in the data segment, unless
they will only be used by a subroutine.

Defining a structure does not reserve any bytes of memory. It is only
when you declare a structured variable that memory is allocated.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

REFERENCING DATA STRUCTURES IN ASSEMBLER

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Well, you've seen how to define structures, but how do you actually refer to
them in your code?

All you have to do, is place a few lines like the ones below somewhere in your
program - preferably in the data segment.

Date STRUC
Day DB 19
Month DB 6
Year DW 1996
Date ENDS

Date_I_Passed_Physics Date <> ; I hope!


At this point in time, Date_I_Passed_Physics has all three of its fields
assigned. Day is set to 19, Month to 6 and Year to 1996. Now, what are
those brackets, "<>", doing after date you ask?

The brackets present us with yet another chance to alter the contents of the
variable's fields. If I had written this:

Date_I_Passed_Physics Date <10,10,1900>


...then the fields would have been changed to the values in the brackets.
Alternatively, it would have been possible to do this:

Date_I_Passed_Physics Date <,10,> ;


And now only the Month field has been changed. Note that in this example,
the second comma was not needed as we did not go on to change further fields.
It is your choice, (and the compiler's!), whether to leave the second comma
in.


Now all this is very well, but how do you use these values in your code? It
is simply a matter of saying:

MOV AX, [Date_I_Passed_Physics.Month] ; or something like

MOV [Date_I_Passed_Physics.Day], 5 ; or maybe even

CMP [Date_I_Passed_Physics.Year], 1996


Simple, huh?

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

CREATING ARRAYS IN ASSEMBLER

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Okay, arrays are pretty easy to implement. As an example, let's say you had
the following array structure in Pascal:

Var
MyArray : Array[0..19] Of Word;


To create a similar array in Assembler, you must use the DUP operator. DUP,
or DUPlicate Variable, has the following syntax:

<label> <directive> <count> DUP (expression)

Where (expression) is an optional value to initialize the array to.


Basically, that Pascal array would look like this:

MyArray DW 20 DUP (?)


Or, if you wanted to initialize each value to zero, then you could say this:

MyArray DW 20 DUP (0)


And, as another example of just how flexible Assembler is, you could say
something along the lines of:

MyArray DB 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10


..to create a 10 byte array with all ten elements initialized to 1, 2, 3...

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

INDEXING ARRAYS IN ASSEMBLER

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Well, now you've seen how to create arrays, I guess you are going to want to
know how to reference individual elements. Well, let's say you had the
following array:

AnotherArray DB 50 DUP (?)


If you wanted to move element 24 into, say, BL, then you could do this:

MOV BL, [AnotherArray + 23] ; Or, it would be possible to say:

MOV AX, 23
MOV BL, [AnotherArray + AX]


NOTE: Do not forget that all arrays start at element ZERO. High-level
languages like C and Pascal make you forget this due to the way they let
you reference arrays.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


Now that was easy, but what if AnotherArray was 50 WORDS, not BYTES?

AnotherArray DW 50 DUP (?) ; like this.


Well, to access element 24, you would have multiply the index value by two,
and then add that to AnotherArray to get the desired element.

MOV AX, 23 ; Access element 24
SHL AX, 1 ; Multiply AX by two
MOV BX, [AnotherArray + AX] ; Get element 24 in BX


Not all that hard, no? However, this method gets a little tricky when you
don't have nice neat little calculations to do when the index is not a power
of two.

Let's say that you had an array that had an element size of 5 bytes.
If we wanted to check the seventh element, we'd have to do something like
this:

MOV AX, 6 ; Get the seventh element
MOV BX, 5 ; Each element is five bytes big
MUL BX ; AX = 6 x 5
MOV DX, [YetAnotherArray + AX] ; Get element 7 in DX


However, as I have stressed before, MUL is not a very efficient way of coding,
so replacing the MUL with a SHL 2 and an ADD would be the order of the day.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


Just before we press on with something else, I guess I'd better take the time
to mention floating point numbers. Now, floating point numbers can get
awkward to manipulate in Assembler, so don't go and write that spreadsheet
program you've always wanted in machine code! However, when working with
texture mapping, circles and other more complicated functions, it is
inevitable that you'll need something to declare floating point numbers.

Let's say we wanted to store Pi. To declare Pi, we need to use the DT
directive. You could declare Pi like this:

Pi DT 3.14


DT actually reserves ten bytes of memory, so it would be possible to declare
Pi to a greater number of decimal places.

I'm not going to go into the specifics of floating point numbers in this
tutorial. When we need them later on, I'll cover them.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


Okay, in the last tutorial I said I'd give some sort of summary of what we've
covered over the last four months. (Hey - that's roughly a tutorial every
two weeks, so maybe they haven't been so wildly erratic after all!)

Anyway, as it happens I'm going to go over getting and setting individual
bits in a register, because this is an important topic that I should have
covered a long time ago.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

LOGICAL OPERATORS

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Okay, way back in Tutorial Five, I gave the three truth tables for AND, OR
and XOR.

(By the way, in one edition of Tutorial Five, I messed up the table for XOR,
kindly pointed out by Keith Weatherby, so if you don't have the most
up-to-date version, (currently V 1.3), then get it now. Please, although I
try my best to weed out any mistakes from the Tutorials, some do get through,
so if you spot any, please let me know.

Make sure you have the most recent editions of the tutorials before you do
this though!)

Okay, enough of my mistakes. Those tables looked like these:


AND OR XOR

0 AND 0 = 0 0 OR 0 = 0 0 XOR 0 = 0
0 AND 1 = 0 0 OR 1 = 1 0 XOR 1 = 1
1 AND 0 = 0 1 OR 0 = 1 1 XOR 0 = 1
1 AND 1 = 1 1 OR 1 = 1 1 XOR 1 = 0


This is all very well, but what use can these be to us? Well, first of all,
lets have a look at what AND can do. We can use AND to mask bits in a
register or variable, and thus set and reset individual bits.

As an example, we will use AND to test a value of a single bit. Look at the
following examples, and see how you can use AND for your own ends. A good
use for AND would be to check if a character read from the keyboard is either
a capital letter or not. (You can do this, because the only difference
between a capital letter and a lowercase letter is one bit.


EG: 'A' = 65 = 01000001
'a' = 97 = 01100001

'S' = 83 = 01010011
's' = 115 = 01110011)

So, in the same way that you can AND the following binary numbers together,
you could use a similar approach to write a routine that checks whether a
character is upper or lower case.


EG: 0101 0011 0111 0011
AND 0010 0000 AND 0010 0000

= 0000 0000 = 0010 0000

^^^ This is upper case ^^^ ^^^ This is lower case ^^^


Now, what about OR? OR is most often used after an AND, but does not have
to be. You can use OR to change individual bits in a register or variable
without changing any of the other bits. You could use OR to write a routine
to make a character uppercase if it is not already, or perhaps lower case if
it was previously upper.


EG: 0101 0011
OR 0010 0000

= 0111 0011

^^^ Capital S has now been changed to lower case s ^^^


The AND/OR combination is one of the most often used tricks of the trade of
Assembler, so make sure you have a good grip on the concept. You will often
see me using them, taking advantage of the speed of the instructions.

Finally, what about XOR? Well, eXclusive OR can be very useful at times. XOR
can be useful in toggling individual bits on and off without having to know
what the contents of each bit was beforehand. Remember, as with OR, a zero
mask allows the original bit to pass through.

EG: 1010 0010
XOR 1110 1011

= 0100 1001


Make some attempt to learn these binary operators, and what they do. They
are an invaluable tool when working with binary numbers.

NOTE: For simplicity, Turbo Assembler allows you to use binary numbers in
your code. EG, it would be possible to say, AND AX, 0001000b instead
of AND AX, 8h to test bit 3 of AX. This can possibly make things
easier for you when coding.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

THE DEMO PROGRAM

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Okay, enough of the boring stuff - on to the demo program I included! I
thought it was time to write another demo - a proper 100% Assembler one this
time, and had a go at a fire routine. Fire routines can look pretty
effective, and are surprisingly easy to make, so why not I thought...

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


Now, the principles of a fire routine are quite simple. You basically do the
following:

Create a buffer to work with

This buffer may be almost any size, though the smaller you make it, the
faster your program will be, and the larger you make it, the more well
defined the fire will be. You need to strike a balance between clarity
and speed.

My routine is a little slow, and this is partly due to the clarity of
the fire. I chose 320 x 104 as my buffer size, so I made a compromise.
The horizontal resolution is good - 1 pixel per array element, but the
vertical resolution is a little low - 2 pixels per array element.

However, I've seen routines where an 80 x 50 buffer is used, meaning
there is both 4 pixels per element for the horizontal and vertical
axis. It's fast, but grainy.


Make a nice palette

It would be good idea to have color 0 as black, (0, 0, 0) and color 255
as white - (63, 63, 63). Everything in between should be a
reddish-yellow flamey mix. I guess you could have green flames if you
wanted, but we'll stick to the flames we know for now. :)


Now the main loop begins. In the loop you must:

Create a random bottom line, or two bottom lines

Basically, you have a loop like:

For X := 1 To Xmax Do
Begin
Temp := Random(256);
Buffer[X, Ymax - 1] := Temp;
Buffer[X, Ymax] := Temp;
End;

Code that in the language of your choice, and you're in business.


Soften the array

Now this is the only tricky bit. What you have to do, is as follows:

* Start from the second row down of the buffer.
* Move down, and for each pixel:

* Add up the values of four pixels that surround the pixel.
* Divide the total by four to get an average.
* Take one from the average.
* Put the average - 1 back into the array DIRECTLY ABOVE where the
old pixel used to be. (You can alter this, and say, put it above
and to the right, and then it will look like the flame is being
blown by the wind.)

* Do this till you get to the last row.


Copy the array to the screen

If your array is 320 x 200, then you can copy element-for-pixel. If it
isn't, then things are harder. What I had to do was copy an array row
to the screen, move down a screen row, copy the same array row to the
screen, and then go onto a different row in the array and screen.

This way, I spread the fire out a bit.

You will of course, wonder exactly why my array is 320 x 104 and not
320 x 100. Well, the reason for this is fairly simple. If I had used
320 x 100 as my array dimensions, and then copied that to the screen,
the last four or so rows would have looked pretty weird. They would
not have been softened properly, and the end result would not be at all
flamey. So, I just copied up to row 100 to the screen, and left the
rest.

As an experiment, try changing the third line below in the DrawScreen
procedure to MOV BX, BufferY and changing the dimensions to
320x100 and see what happens.

MOV SI, OFFSET Buffer ; Point SI to the start of the buffer
XOR DI, DI ; Start drawing at 0, 0
MOV BX, BufferY - 4 ; Miss the last four lines from the
; buffer. These lines will not look
; fire-like at all

Loop back to the top.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


Well, no matter how well I explained all that, it's very hard to actually
see what's going on without looking at some code. So now we'll step through
the program, following what's going on.

Well, first of all, you have the header.


.MODEL SMALL ; Data segment < 64K, code segment < 64K
.STACK 200H ; Set up 512 bytes of stack space
.386


Here, I have said that the program will have a code segment and data segment
total of less than 128K. I go onto to give the program a 512 byte stack, and
then allow 386 instructions.


.DATA

CR EQU 13
LF EQU 10


The data segment begins, and I give CR and LF the carriage return and line
feed values.


BufferX EQU 320 ; Width of screen buffer
BufferY EQU 104 ; Height of screen buffer

AllDone DB CR, LF, "That was:"
DB CR, LF
DB CR, LF, " FFFFFFFFF IIIIIII RRRRRRRRR ..."
DB CR, LF, " FFF III RRR RRR ..."
DB CR, LF, " FFF III RRR RRR ..."
DB CR, LF, " FFF III RRRRRRRR ..."
DB CR, LF, " FFFFFFF III RRRRRRRR ..."
DB CR, LF, " FFF III RRR RRR ..."
DB CR, LF, " FFF III RRR RRR ..."
DB CR, LF, " FFF III RRR RRR ..."
DB CR, LF, " FFFFF IIIIIII RRRR RRRR ..."
DB CR, LF
DB CR, LF
DB CR, LF, " The demo program from Assembler Tutorial 8. ..."
DB CR, LF, " author, Adam Hyde, at: ", CR, LF
DB CR, LF, " blackcat@faroc.com.au"
DB CR, LF, " [url="http://www.faroc.com.au/~blackcat""]http://www.faroc.com.au/~blackcat"[/url], CR, LF, "$"

Buffer DB BufferX * BufferY DUP (?) ; The screen buffer

Seed DW 3749h ; The seed value, and half of my
; phone number - not in hex though. :)

INCLUDE PALETTE.DAT ; The palette, generated with
; Autodesk Animator, and a simple
; Pascal program.


Now, at the end, I declare the array and declare a SEED VALUE for the Random
procedure that follows. The seed is just a number that is necessary to start
the Random procedure off, and can be anything you want it to.

I have also saved some space and put the data for the palette into an external
file which is included during assembly. Have a look inside the file. Being
able to use INCLUDE can save a lot of space and confusion.


I've skipped through some procedures that are fairly self-explanatory, and
moved onto the DrawScreen procedure.


DrawScreen PROC
MOV SI, OFFSET Buffer ; Point SI to the start of the buffer
XOR DI, DI ; Start drawing at 0, 0
MOV BX, BufferY - 4 ; Miss the last four lines from the
; buffer. These lines will not look
; fire-like at all
Row:
MOV CX, BufferX SHR 1 ; 160 WORDS
REP MOVSW ; Move them
SUB SI, 320 ; Go back to the start of the array row
MOV CX, BufferX SHR 1 ; 160 WORDS
REP MOVSW ; Move them
DEC BX ; Decrease the number of VGA rows left
JNZ Row ; Are we finished?
RET
DrawScreen ENDP


This is also easy to follow, and takes advantage of MOVSW, using it to move
data between DS:SI and ES:DI.


AveragePixels PROC
MOV CX, BufferX * BufferY - BufferX * 2 ; Alter all of the buffer,
; except for the first row and
; last row
MOV SI, OFFSET Buffer + 320 ; Start from the second row

Alter:
XOR AX, AX ; Zero out AX
MOV AL, DS:[SI] ; Get the value of the current pixel
ADD AL, DS:[SI+1] ; Get the value of pixel to the right
ADC AH, 0
ADD AL, DS:[SI-1] ; Get the value of pixel to the left
ADC AH, 0
ADD AL, DS:[SI+BufferX] ; Get the value of the pixel underneath
ADC AH, 0
SHR AX, 2 ; Divide the total by four

JZ NextPixel ; Is the result zero?
DEC AX ; No, so decrement it by one



NOTE: ONE is the decay value. If you were to change the line above to, say
SUB AX, 2 you would find that the fire would not reach so high.
Experiment...be creative! :)


NextPixel:
MOV DS:[SI-BufferX], AL ; Put the new value into the array
INC SI ; Next pixel
DEC CX ; One less to do
JNZ Alter ; Have we done them all?
RET
AveragePixels ENDP


Now we've seen the procedure that does all the softening. Basically, we just
have a loop that adds up the color values of the pixels around it, carrying
the values of the pixels before. When it has the lot, the total - held in AX,
is divided by four to get an average. The average is then plotted directly
above the current pixel.

For more information regarding the ADC instruction, look it up in Tutorial 5,
and look at the programs below:

Var Var
W : Word; W : Word;

Begin Begin
Asm Asm
MOV AL, 255 MOV AL, 255
ADD AL, 1 ADD AL, 1
MOV AH, 0 MOV W, AX
ADC AH, 0 End;
MOV W, AX
End; Write(W);
End;
Write(W);
End;

^^^ This program returns 256 ^^^ This program returns 0


Remember that ADC is used to make sure that when a register or variable is
not big enough to hold a result, the result is not lost.


Okay, after skipping a few more irrelevant procedures, we come to the main
body, which goes something like this:


Start:
MOV AX, @DATA
MOV DS, AX ; DS now points to the data segment.


We firstly point DS to the data segment, so we can access all our variables.


CALL InitializeMCGA
CALL SetUpPalette

MainLoop:
CALL AveragePixels

MOV SI, OFFSET Buffer + BufferX * BufferY - BufferX SHL 1
; SI now points to the start of the second last row
MOV CX, BufferX SHL 1 ; Prepare to get BufferX x 2 random #s

BottomLine:
CALL Random ; Get a random number
MOV DS:[SI], DL ; Use only the low byte of DX - ie,
INC SI ; the number will be 0 --> 255
DEC CX ; One less pixel to do
JNZ BottomLine ; Are we done yet?


Here, a new bottom line is calculated. The random procedure - many thanks to
it's unknown USENET author - returns a very high value in DX:AX. However,
we only require a number from 0 to 255, so by using only DL, we have such a
number.


CALL DrawScreen ; Copy the buffer to the VGA

MOV AH, 01H ; Check for keypress
INT 16H ; Is a key waiting in the buffer?
JZ MainLoop ; No, keep on going

MOV AH, 00H ; Yes, so get the key
INT 16H

CALL TextMode
MOV AH, 4CH
MOV AL, 00H
INT 21H ; Return to DOS
END Start


And I think the end part is also pretty easy to understand. I've tried to
comment the source as much as I can, perhaps a little too heavily in some
parts, but I hope by now everyone has an idea of how a fire routine works.

Anyway, the goal was not to teach you how to make a fire routine, but how to
use arrays, so if you got the fire routine stuff too, then that's an added
bonus. I referred to my arrays slightly differently to how I explained in
this tutorial, but the theory is still the same, and it shows you other ways
of doing things. If you didn't get how to use arrays from that, then maybe
you never will, at least not with my tutorials anyway. Hey, go buy a $50
book! :)


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Next week's tutorial will include:

File I/O
Using Assembler with C/C++
Lookup tables?
Macros.

If you wish to see a topic discussed in a future tutorial, then mail me, and
I'll see what I can do.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Don't miss out!!! Download next week's tutorial from my homepage at:

http://www.faroc.com.au/~blackcat


See you next week!

- Adam.

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Yet another joke I grabbed off a local BBS:

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


If God Was A Computer Programmer:

Some important theological questions can best be answered by thinking of
God as a computer programmer.

Q: Did God really create the world in seven days?
A: He did it in six days and nights while living on cola and candy bars.
On the seventh day he went home and found out his girlfriend had left him.

Q: What causes God to intervene in earthly affairs?
A: If a critical error occurs, the system pages him automatically and he logs
on from home to try to bring it up. Otherwise, things can wait until
tomorrow.

Q: How come the Age of Miracles ended?
A: That was the development phase of the project.
Now we're in the maintenance phase.

Q: Who is Satan?
A: Satan is an MIS director who takes credit for more powers than he actually
possesses, so nonprogrammers become scared of him. God thinks he's
irritating but irrelevant.

Q: Why does God allow evil to happen?
A: God thought he eliminated evil in one of the earlier revs.

Q: How can I protect myself from evil?
A: Change your password every month and don't make it a name, a common word,
or a date like your birthday.

Q: If I pray to God, will he listen?
A: You can waste his time telling him what to do, or you can just get off his
back and let him program.

Q: Some people claim they hear the voice of God. Is this true?
A: They are much more likely to receive email.
"To try is to fail....so to succeed is to be lucky"

#10 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 20 November 2006 - 02:20 PM

浜様様様様様様様様様様様様様様様様様様様様様様様様様様様融
Adam's Assembler Tutorial 1.0 把

PART IX
藩冤様様様様様様様様様様様様様様様様様様様様様様様様様様夕
青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Revision : 1.0
Date : 19-12-1996
Contact : blackcat@faroc.com.au
http://www.faroc.com.au/~blackcat

Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are
reserved by the author. You may freely redistribute only the
ORIGINAL archive, and the tutorials should not be edited in any
form.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Well, this tutorial really is late out and I feel particularly guilty about
it. Nonetheless, I've included yet another demo program with this edition
so that makes up for it a little bit.

Just a word about last Tutorial Eight's demo program - FIRE!. After obtaining
a later version of TASM, I discovered that FIRE! doesn't like compiling all
that well, so make sure you have the latest revision of Tutorial Eight (V 1.4)
with the bugfix. You can always get all the latest revisions of the tutorials
straight from ftp.faroc.com.au in the /pub/blackcat/programming/tutorials
directory, and this is highly reccommended.

Anyway, on we go with:


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

FILE I/O

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Sooner or later, you will want to mess around with files. All you have
to bear in mind here is that everything is HANDLE BASED. Those of you who
have used or experimented with XMS will realize exactly what I mean by
handles, but if you don't, then here's a quick summary:

* You open/create a file.
* You get a 16-bit unsigned integer to reference it with.

How hard is that?


Note: Back in the days before DOS 2, you had to use File Control Blocks to
reference your files. (You've probably seen FCBS=xxxx sometime before
in CONFIG.SYS files, and this is to support programs that were designed
for XT's.) We can forget all about FCBs now, as they are pretty much
obsolete.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Opening A File: ( Interrupt 21H )

AH = 3DH
AL = operation type:

0 = Read only operation;
1 = Write only operation;
2 = Read/write operation.

DS:DX = Filename


Returns:

If successful, the carry flag is cleared to zero, and the file handle is
returned in AX. However, if something went wrong, the carry flag is set
to one, and the error code returned in AX. For a list of all the error
codes, see the table further down.


Now, after all that, an example:


.MODEL SMALL
.STACK 200H
.DATA

FileName DB "EXAMPLE.TXT$"
Error DB "Uh oh$"

.CODE

START:

MOV AX, @DATA ; Point AX to the data segment
MOV DS, AX ; AX --> DX
MOV DX, OFFSET FileName ; Put offset of the file to open in DX
MOV AH, 3DH ; Open file
MOV AL, 00H ; Read only
INT 21H

JC Problem ; Something happen?

; Here you would get the handle from AX, and do some stuff

JMP Done ; Nope

Problem:

MOV DX, OFFSET Error ; Uh oh
MOV AH, 09H
INT 21H

Done:

MOV AX, 4C00H ; Jump back to DOS - closing any open
INT 21H ; files. Slack, but we don't know how
; to close files yet.
END START


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Okay... simple enough I hope. Now, suppose we want to create a new file?
It's just another simple subfunction of interrupt 21H. This is how you do
it:


Creating A New File: ( Interrupt 21H )

AH = 3CH
CX = file type:

0 = Normal file;
1 = Read only;
2 = Hidden file;
4 = System file;

DS:DX = Filename


Returns:

As before, if successful, the carry flag is cleared to zero, and the file
handle is returned in AX. Note that you must watch out for existing files
before creating a new file of the same name. DOS will not check if a file
of the same name already exists, and overwrites the old one.

Before you create a new file - try to open the file first. If you get
error code 2 in AX, (file does not exist), then go ahead and create a new
file. If you don't get error 2, you'll be overwriting an existing file!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

As you should know from experience with high-level languages, you must close
your files before you end your program. (Actually, function 4CH closes all
open files anyway, but that's a slack way of going about things.) To close
an open file, you should do this:


Closing A File: ( Interrupt 21H )

AH = 3EH
BX = file handle


Returns:

Yet again, any errors are reflected in the carry flag and AX.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Finally, error codes. Just checking CF to see if anything went wrong will
certainly let us know if anything is amiss, but we'd really like more detail.
Examining AX after an error is detected is the way to go, and AX could
contain any one of the following codes:


浜様様様様様様用様様様様様様様様様様様様様様様様様様様様様様様
Error Code Explanation
把陳陳陳陳陳陳津陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳
00H Unknown error
01H Invalid function number
02H File not found
03H Path not found
04H Too many open files
05H Access denied
06H Invalid handle
07H Control blocks destroyed
08H Out of memory
09H Bad control block address
0AH Invalid environment
0BH Invalid format
0CH Invalid access code
0DH Invalid data
0EH Unkown error
0FH Invalid drive
10H Cannot remove current directory
11H Device not the same
12H No more files available
13H Disk is write-protected
14H Bad unit
15H Drive not ready
16H Unknown command
17H CRC error
18H Bad structure length
19H Seek error
1AH Invalid medium
1BH Sector not found
1CH Printer out **
1DH Write error
1EH Read error
1FH General failure
藩様様様様様様溶様様様様様様様様様様様様様様様様様様様様様様様


** I know, I don't get it either. I guess it's in there because DOS treats
everything as a file.

Phew! All courtesy of the good old DOS technical reference. Some of the
ones up there were pretty obscure - there's only a few you really need to
remember. Some of my *favourites* are: Sector not found, Seek error and
CRC error half way through a stack of arjed diskettes. Gee that brings back
memories. :)


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Okay, so we've seen how to create, open and close files. Now let's do
something with 'em. To read some bytes from a file, you must use function
3FH. Assuming you've already opened the file you want to read from, you can
now use a bit of code like below:


MOV AH, 3FH ; Read byte(s)
MOV BX, Handle ; Fileto work on
MOV CX, BytesToRead ; How much to get
MOV DX, OFFSET WhereToPutThem ; An array or variable
INT 21H

JC DidSomethingGoWrong ; Check for an error


If you are having problems grasping any of this - don't worry too much. Just
go back over the examples above and see what sense you can make of it. Next
tutorial we'll press on with sprites - (and loading them from disk) - so
you'll get to see a good example.

Okay... now, writing to a file. Much the same as reading, we now use function
40H. Some code to write a byte would look like:


MOV AH, 40H ; Write byte(s)
MOV BX, Handle ; Fileto write to
MOV CX, BytesToWrite ; How much to write
MOV DX, OFFSET WhereToWriteFrom ; Where the data is coming from
INT 21H
JC DidSomethingGoWrong ; Any errors?


Well, that just about concludes file I/O for this tutorial. Although not a
major component of Assembly language programming, file I/O is nonetheless
an important concept to get a grip on.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

CALLING ASSEMBLER FROM C/C++

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


I suppose it's about time I covered linking assembler with C. Personally, I
prefer doing VGA coding in an Assembler/Pascal combination. However, C has
its place, and linking with C is an important aspect we should cover.


You should have realized that you can start entering Assembler code into your
C program like so:


/* Your C code goes here */

asm {
/* */
/* Your assembler code goes here */
/* */
}

/* Your C code resumes here */


Now, considering that we can insert Assembler straight into C code, why would
we bother to write external code? The answer is fairly simple. By using
external routines, we have code that is faster to execute, faster to compile,
can use some of Turbo Assembler's special features - such as ideal mode, and
may even be portable to other languages.

Writing external code for C is fairly simple, and is thankfully more easy than
writing external code for Pascal. (See Tutorial Seven). As you noticed back
in Tutorial seven, we had to declare the code and data segments ourselves
using the somewhat messy SEGMENT directive. This is due to the way that
Pascal likes to organise memory, and there is only one way we can get around
the problem - we can use the TPASCAL model. Unfortunately, TPASCAL is an
antiquated and outdated way of going about things, so we have to put a bit of
work in. I'm not going to mention TPASCAL ever again, so we can safely forget
about the gritty details.


Note that none of this applies to us in C - we can quite happily use our nice
simple assembler skeletons. There are a few restrictions placed upon us by
most compilers though:


The compiler uses SI and DI to store register variables. If you have
used register variables in your code, remember to push and pop SI and
DI in your external code.

The compiler probably will not push and pop CS, DS, SS and BP so ensure
that you are careful if you alter any of these registers.


Other than those little snippets, there is little else we need to bear in
mind. Let's blaze!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Okay... now we're going to write a small external routine and link it to C.
Let's take a look at a basic skeleton that just puts some text onto the
screen.


============================ LIBRARY.ASM =============================


.MODEL SMALL
.DATA

Message DB "Well looky here - we got ourselves some text$"

.CODE

PUBLIC _sample

; ---------------------------------------------------------------------------

;
; void sample();
;

_sample PROC NEAR ; Declare a near procedure

MOV AH, 00H ; Set video mode
MOV AL, 03H ; Mode 03H
INT 10H

MOV AH, 09H ; Print a string
MOV DX, OFFSET Message ; DS:DX <-- Message
INT 21H

RET ; Outta here!

_sample ENDP

END


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Well.... nothing too tricky there. Now, what about the C code that goes along
with it?


============================= EXAMPLE.C ==============================


extern void sample();

int main()
{

sample();
return 0;

}


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

And to compile the lot, the line below'll do the job.

C:\> TCC EXAMPLE.C LIBRARY.ASM


Of course, if you are using another flavour of C then replace TCC with
whatever command line interpreter you have. It's also possible to get C to
recognise variables declared in Assembler, and the following skeleton details
how this is done:


============================ LIBRARY.ASM =============================


.MODEL SMALL
.DATA

PUBLIC _YourVariable ; Declare an external variable

_YourVariable DW 9999 ; Make that variable a word with a value of 9999

.CODE

END



============================= EXAMPLE.C ==============================


extern int YourVariable;

int main()
{

printf("The Assembler external variable is: %d", YourVariable);
return(0);

}


Again, compile this with: TCC EXAMPLE.C LIBRARY.ASM


But what about passing parameters to your routines? We could do this the hard
way like we did with Pascal, or alternatively we could use the ARG directive.

ARG is brilliant, because it greatly simplifies things -- but it has some
shortcomings. Namely, in every routine you need an additional three
instructions. If you want speed and don't mind a bit of hard work, work with
the stack directly like we did in Tutorial Seven.

Here's how you use ARG:


============================ LIBRARY.ASM =============================


.MODEL SMALL
.DATA
.CODE

PUBLIC _putpixel ; Declare the external procedure

; ---------------------------------------------------------------------------
;
; void putpixel(int x, int y, char color, int location);
;

_putpixel PROC NEAR

ARG X : Word, Y : Word, Color : Byte, Location : Word

PUSH BP ; Save BP
MOV BP, SP ; BP *must* equal SP for ARG to work

MOV AX, [Location] ; Parameters can now be accessed easily
MOV ES, AX
MOV BX, [X]
MOV DX, [Y]
MOV DI, BX
MOV BX, DX
SHL DX, 8
SHL BX, 6
ADD DX, BX
ADD DI, DX
MOV AL, [Color]
MOV ES:[DI], AL

POP BP ; BP needs to be restored!

RET

_putpixel ENDP

END


============================= EXAMPLE.C ==============================


extern void putpixel(int x, int y, char color, int location);

int main()
{

asm {
mov ax, 0x13
int 0x10
}

putpixel(100, 100, 12, 0xa000);
sleep(2);

asm {
mov ax, 0x03
int 0x10
}

return(0);
}



Not all that tricky, huh? However, if you choose to write external
routines because you want the speed Assembler can give, then access the stack
the hard way. Those extra pops and pushes can really add up if your putpixel
routine gets called 320x200 times!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

AN INTRODUCTION TO MACROS

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


Macros are one of the most powerful features you have at your disposal when
you are working with Assembler. Often you will find yourself repeating the
same few lines of code over and over again when writing larger programs. You
don't want to go to the trouble of creating a procedure -- which would slow
down the code, but you don't want to keep repeating yourself.

The answer.... MACROS.


A macro is just a set of instructions that are given a name by which it will
be referred to in the code. You can define a macro like this:


MyMacroName MACRO

;
; Your instructions go here
;

ENDM MyMacroName


And from then on, whenever you put MyMacroName in your code, the
instructions contained within the macro will be assembled in place of the
macro name.


NOTE: It is probably best to declare any macros just before you declare the
data segment. For clarity, place all your macros in another text file
and then use INCLUDE <filename> to include the macros.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Macros may also have parameters, making them especially handy. For example,
I have often used DOS function 09H to put a string on the screen. I could
make the programs I write more easy to read at first glance by creating the
following macro:

PutText MACRO TextParam

MOV AH, 09H ; TextParam is the parameter -- NOT
MOV DX, OFFSET TextParam ; a variable. Replace TextParam with
INT 21H ; whatever name you choose.

ENDM PutText


Then, assuming in the data segment I had declared a string like this:

AString DB "This is a string$"


I could display that string by writing:

PutText AString


NOTE: When you are working with macros, be careful to observe what
registers they change. If in doubt, push and then pop any
registers you feel may be affected.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Although that simple macro wasn't really anything special, macros have many
other uses. I'm not going to say anything more on macros for now, but I'll
use them from time to time in future demo programs, and you'll learn other
techniques you can put to good use.

Anyway, on with what I've been wanting to do:


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

敖陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳朕

THE DEMO PROGRAM

青陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳潰


At first I was going to release this tutorial without a demo program, but
seeing as I've been a bit too lazy for too long, (and also because a friend of
mine just made a demo like this), I've decided to include a plasma.

Plasmas can be a bit tricky in places -- it took me a while to get the whole
thing working properly because of a problem I had with my lookup table. But
if you follow the algorithm set out below, you shouldn't have any problems.


*** Before you begin, you'll need FOUR temporary variables in your code. ***
In Assembler this can get a bit tricky because you will often find
yourself running out of registers. You could declare some bytes up
in the data segment, but it's faster to use registers. These four
temporary variables will only hold numbers between 0 and 255, so
they only need to be BYTES.

In the algorithm, I refer to these temporary variables as Temp1,
*** Temp2, Temp3 and Temp4. ***


The algorithm looks like this:


Create a lookup table

This is basically just one long sine wave. You can experiment by using
a cosine wave instead, or alter the amplitude of the function you are
using. I created my lookup table using the following expression:

For W := 1 To 512 Do
SinTable[W] := Round(Sin(W / 255 * Pi * 2) * 128);

(SinTable is an array of 512 BYTES)


Initialize the palette

I personally like to make my palettes after I see the demo running with
the standard palette. That way, by making certain colors dark and others
very light, the result is exactly the way I want it. I've found the best
way to do this is to grab the screen when the demo is running with a
program like Screen Thief, then load up that picture in a paint program
that lets you alter the palette.

Once you get the palette how you want it, save it to disk as a COL file
(if possible) and then write a little program to read in the COL file and
write the file so it looks like PLASMA.DAT.

Remember, Screen Thief is shareware, so if you use it, send the author
some money huh?


Loop (1): Before you start plotting the first row, you must:

* Zero out Temp4;
* Decrement Temp3 by two;

You can experiment with Temp3 -- the larger the number you
subtract from it, the faster the plasma will 'move'.

You now move onto Loop (2).


Loop (2): At the start of each row you must:

* Increment Temp4 by one;
* Let Temp1 = Sintable[Current row + Temp3];
* Let Temp2 = SinTable[Temp4];

You now move onto Loop (3).


Loop (3): For every pixel on the current row you must:

* Work out the color of that pixel to be plotted;

The color value of that pixel is quite simply:

SinTable[Temp1 + Temp2] + SinTable[Current row + Temp2]

Unfortunately, this is a little harder to work out in Assembler
and ends up taking several lines of code!!


* Increment Temp1 by one;
* Increment Temp2 by one;


After doing an entire row, you then loop back to Loop (2).

Once you have done all the rows (200), you can then loop back to loop (1).



Of course, you'll also want to put a check for retrace in there, and checking
to see if someone hit a key would be a good idea!!


NOTE: For those that didn't know, the VGA has a status register that is worth
paying particular attention to. This is register 03DAH, and by
checking its various bits we can see what's happening right now with
the VGA.

(For those who want to know exactly what all the other bits are for,
you should obtain a copy Ralf Brown's Interrupt List. This is
available from my homepage and contains a complete list of all the
interrupts, registers and much more.)

Anyway, we are only interested in bit four of 03DAH which lets us know
is a retrace is in progress. If we can access the VGA while the
electron gun in the monitor is retracing it's path to the top of the
screen -- we get fast, flicker-free access. What's more, because
retrace happens every 1/80th of a second ON ALL COMPUTERS, we have a
method of locking our demo to a particular speed on all machines.

To check if retrace is happening, we simply examine bit four. If bit
four is set, a retrace is now in progress. However, we don't know
just how far into a retrace it is and we don't know how much time of
flicker-free access we have. The solution is to check again for a
retrace so we can be sure that we are at the START of one.

I've used a retrace in the code to basically make sure the demo runs
at the same speed on all machines. (More or less anyway).


Also note that my plasma is more of a plasma variant. You can, and
are encouraged to alter the code -- (try incrementing the temp values
instead of decrementing, changing the amount the temp values are
decremented by or changing the way the color value is found. Also try
changing the palette, because this can make the plasma appear
completely different).

It is possible to create all sorts of effects by only making simple
changes, so experiment... be creative!


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

Well, that just about concludes things for this tutorial. I'm not exactly
sure what I'll be putting in Tutorial Ten -- sprites will be covered, but I
can see the tutorials leaning towards demo effects and how you can code them
in standalone Assembler.

Until next time,

-- Adam.


陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳

This is getting a bit old now, but is still amusing:

陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳陳


If Operating Systems Were Beers...


DOS Beer: Requires you to use your own can opener, and requires you to read
the directions carefully before opening the can. Originally only
came in an 8-oz. can, but now comes in a 16-oz. can. However, the
can is divided into 8 compartments of 2 oz. each, which have to be
accessed separately. Soon to be discontinued, although a lot of
people are going to keep drinking it after it's no longer
available.

Mac Beer: At first, came only a 16-oz. can, but now comes in a 32-oz. can.
Considered by many to be a "light" beer. All the cans look
identical. When you take one from the fridge, it opens itself.
The ingredients list is not on the can. If you call to ask about
the ingredients, you are told that "you don't need to know." A
notice on the side reminds you to drag your empties to the
trashcan.

3.1 Beer: The world's most popular. Comes in a 16-oz. can that looks a
lot like Mac Beer's. Requires that you already own a DOS Beer.
Claims that it allows you to drink several DOS Beers
simultaneously,but in reality you can only drink a few of them,
very slowly, especially slowly if you are drinking the Windows Beer
at the same time. Sometimes, for apparently no reason, a can
of Windows Beer will explode when you open it..

OS/2 Beer: Comes in a 32-oz can. Does allow you to drink several DOS Beers
simultaneously. Allows you to drink Windows 3.1 Beer
simultaneously too, but somewhat slower. Advertises that its cans
won't explode when you open them, even if you shake them up. You
never really see anyone drinking OS/2 Beer, but the manufacturer
(International Beer Manufacturing) claims that 9 million six-packs
have been sold.

95 Beer: You can't buy it yet, but a lot of people have taste-tested it and
claim it's wonderful. The can looks a lot like Mac Beer's can, but
tastes more like Windows 3.1 Beer. It comes in 32-oz. cans, but
when you look inside, the cans only have 16 oz. of beer in them.
Most people will probably keep drinking Windows 3.1 Beer until
their friends try Windows 95 Beer and say they like it. The
ingredients list, when you look at the small print, has some of the
same ingredients that come in DOS beer, even though the
manufacturer claims that this is an entirely new brew.

NT Beer: Comes in 32-oz. cans, but you can only buy it by the truckload.
This causes most people to have to go out and buy bigger
refrigerators. The can looks just like Windows 3.1 Beer's, but
the company promises to change the can to look just like Windows
95 Beer's - after Windows 95 beer starts shipping. Touted as an
"industrial strength" beer, and suggested only for use in bars.

Unix Beer: Comes in several different brands, in cans ranging from 8 oz. to
64 oz. Drinkers of Unix Beer display fierce brand loyalty, even
though they claim that all the different brands taste almost
identical. Sometimes the pop-tops break off when you try to open
them, so you have to have your own can opener around for those
occasions, in which case you either need a complete set of
instructions, or a friend who has been drinking Unix Beer for
several years.

VMS Beer: Requires minimal user interaction, except for popping the top and
sipping. However cans have been known on occasion to explode, or
contain extremely un-beer-like contents.
"To try is to fail....so to succeed is to be lucky"

#11 White Scorpion

White Scorpion

    Sergeant First Class

  • Sergeant Major
  • 674 posts

Posted 20 November 2006 - 02:47 PM

Backslash > it's great that your posting tutorials like this, but do me a favor, next time use some markup code to make the text a lot better to read. Even scrolling through the page already gave me a headache. Let alone i actually had to read it :P

Nevertheless, nice job ;)
The path of access leads to the server of wisdom..

The Syringe - My Latest Project.
Errors, Vulnerabilities & Exploits explained.
----
www.white-scorpion.nl
www.info-sec.eu
www.info-sec.info

#12 Jeffrey

Jeffrey

    Specialist

  • Sergeant Major
  • 1,109 posts

Posted 20 November 2006 - 08:06 PM

Yeah I am with Scorpion on this one. Next time format it before you post, for example remove all non-standard characters, and maybe even bold where chapters start so everything doesnt run together :P

Good find though, will help me out on my Learning Assembly venture....

#13 t3ctrix

t3ctrix

    Private First Class

  • Members
  • 80 posts

Posted 21 November 2006 - 06:17 AM

This Copy-Paste thing is tiresome. Here's the whole 9 Part Tutorial bundled up in a single text file.

Attached File  Adam__s_Assembler_Tutorial.txt   177.25KB   18 downloads
"Nothing is what it seems remember?"

#14 backslash

backslash

    Private First Class

  • Members
  • 32 posts

Posted 21 November 2006 - 07:30 AM

This Copy-Paste thing is tiresome. Here's the whole 9 Part Tutorial bundled up in a single text file.

Attached File  Adam__s_Assembler_Tutorial.txt   177.25KB   18 downloads



:D yea would of done that but i wasnt a member at the time ;) thanks for the single file.


EDIT: P.S. Now that im a member i can upload, here is the orig. zip along with some example files Enjoi

Attached Files


"To try is to fail....so to succeed is to be lucky"




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users