Issue #4
Saturday, October 6th, 2001

As most QB programmers know, the future of QB programs will rely more and more on the use of assembly code to boost the performance of their programs. Assembly is not an easy thing to learn. One look at assembly code can cause quite a strain on your brain if you never used it before. But, like any other language, if you use it enough, it will be very easy for you to use and understand, and given time you will be able to just glance at some code and be able to understand what it does.

This is the first in a series of articles that will hopefully help you to understand and succesfully program in assembly. Don't expect to learn this overnight though. You must use it constantly to get good at it. Just like when you first learned BASIC. The best way to learn a new language is by doing things in it and not just by reading a tutorial such as this. Plus you should actually WANT to learn assembly. If you don't really want to then stop now.

Alrighty then. First off, why even use assembly code? It's an old language created sometime in the 50's and 60's so why should we bother with it? Well it's really fast and it can allow you to do things you normally can't do in QB (such as SVGA programming). But before you start programming your entire game in assembly code, just remember that you should really only use it for things that need to be done quickly and things that your programming language can't do.

Well that's all fine you say, but how do you use it in QB? Well to do this you basically get to choose one of two methods. The first method involves writing your assembly code in a text file then assembling it into an OBJ file with an assembler such as TASM, MASM, NASM, a86, etc. Then you link it into a library file and load that library into QB! This is the preferred method and I will show you how to do this.

The other method is the only way to use assembly in QBasic 1.1. It involves using CALL ABSOLUTE. First you translate your assembly code into straight machine language and store it in a string. Then you pass the address of the string to CALL ABSOLUTE along with your parameters and presto! But this requires you to find an assembler that can produce raw machine code (DEBUG can do this). Plus you waste program memory storing the code in strings. Some old libraries like Blast! used this method.

For this assembly series I will show you how to use the first method using the TASM assembler.

In assembly you will make alot of use out of the memory. You should be familiar with addressing positions in memory as well. Conventional memory is split up into chunks called segments. These are 16 bytes in size. You always address memory with a segment/offset pair. Say you wanted to access memory position 18. You would use a segment of 1 (16 bytes) and an offset of 2. (16+2=18). Here's another example. To access memory position 37 you would use a segment of 2 and an offset of 5. Simple.

Some data types you should be familiar with are the bit, byte, word, and double word.

A bit is the smallest form of data a computer can use. It is either on (1) or off (0).

A byte is 8 bits long. It is the amount of memory that computers use to store on character such as the letter A. When a byte is unsigned the largest value it can hold is 255. The smallest being 0. When a byte is signed the largest value it can hold is 127 and the smallest is -128.

A word is two bytes long or 16 bits. When it's unsigned it can hold values in the range 65535 to 0. When it's signed 32767 to -32768 (This is the same as QB's integer).

A double word is 4 bytes in length or 32 bits. When it's signed it can hold 4294967296 to 0 When it's signed 2147483647 to -2147483648 (This is the same as QB's long integer).

Your computers CPU contains a bunch of little registers. If you computer is a 386+ then the registers are 32-bits long. If your computer is a 286 or below then the registers are 16-bits long. This article will assume you have 32-bit registers =). Registers are places for the CPU to store things. There are similar to variables. You can add, subtract, multiply, and divide them. But you have to be careful when using them because you can easily run out! There are only a few. Here are most of the registers:

AX, BX, CX, DX	-These are the mostly used ones. They can be used for just about anything.

CS		-This points to the current code segment
DS		-This points to the data segment
ES		-This is an extra segment register that can be used for a bunch of stuff
SS		-This points to the stack's segment
FS		-Similar to ES
GS		-Same as above
IP		-Instruction pointer. This is used with CS
SP		-Stack pointer. This is used with SS
BP		-Base pointer (points to the base of the stack)
SI		-Used mainly with DS
DI		-Used mainly with ES

A pointer is a variable/register that holds the address of something. You'll encounter these alot in C/C++. FS and GS are really used in protected mode but they can be used like ES. I've never had to use them though. Also I wouldn't modify CS, SS, IP, or SP. CS and IP point to the current instruction being executed and even if they could be changed, it would crash your computer. SS and SP point to the stack, which your computer uses constantly. It is alright to USE the values but you should never try to change them. Also ES and DS can't be assigned an immediate number. You must move a number into another register then move it into ES or DS.

Some registers of interest there are ES, DS, SI, and DI. ES and DI are a segment and offset register pair. ES stores the segment and DI stores the offset of something you want to access in RAM. The same goes with DS and SI. When you access what is being pointed to by ES, the computer will use DI as the offset by default unless you specify some other register. Same goes for DS and SI. It's a good thing to keep in mind sometimes. ;)

The first four registers can be split in two parts each.

AX - AH	- The high byte of AX
     AL - The low byte of AX

BX - BH - The high byte of BX
     BL - The low byte of BX

CX - CH - The high byte of CX
     CL - The low byte of CX

DX - DH - The high byte of DX
     DL - The low byte of DX

If you have to put byte into a register to store it for later, you're probably better off putting that byte into one of the high or low bytes of a register rather than the whole register. It'll save some of the precious registers for something else. But note that putting a value into AX, even if its only 1 byte, will clear both AH and AL. Putting something into AL will affect AX but not AH. This goes for BX, CX, and DX as well.

So lets learn some opcodes! We'll start out with the widely used MOV command. MOV puts a value into a another register, memory location, etc. The syntax for MOV is simple:

MOV destination, source

Destination is where the source is being put. Note that MOV doesn't MOVE a value but it copies the value. So something like

MOV AX, 9

will move 9 into AX. If you add an 'h' onto the end of the 9 it will be 9 in hex.

MOV AX, CX

That will copy the contents of CX into AX. Simple. Now lets get into simple math. To add things in asm you use ADD. To subtract use SUB. Bet you didn't see that one coming did you? =).

ADD destination, source
SUB destination, source

destination is where the result of the operation is going to go. Source is what is being added/subtracted. Some examples

ADD BX, 9	;Adds 9 with the value of BX
ADD CX, DX	;Adds DX with CX
SUB AX, 32h	;Subtracts 32 (hex) from AX
SUB CL,	2	;Subtracts 2 from CL

Easy right? Note: A ';' is a comment in asm. Now how about divisions and multiplications? Well just before I show you those, there is something you should know. Multiplying and dividing is SLOW! There is a somewhat limited way around this slowness but I'll show you it later.

MUL source
DIV source

Notice something? They only take one operand. This is because the result of the operation goes into AX. So whatever you pass will be multiplied/divided with AX. Also the source can't be a direct number. It must be a register or memory position. I can only guess as to the reasons for this...

MOV AX, 2	;Moves 2 into AX
MOV DX, 4	;Moves 4 into DX
MUL DX 	        ;Multiplies AX by BX

After this AX will equal 8.

MOV AX, 5	;Moves 5 into AX
MOV DX,	2	;Moves 2 into DX
DIV DX		;Divides AX with DX

Afterwords AX will be 2 and DX will be 1 (the remainder). DX will always be the remainder if there is any.

Here's a tip. Instead of doing ADD AX, 1 you can do the much quicker INC AX. INC is a command that increases the passed operand by 1. The operand must be either a register or a memory location. Also to decrease by 1 faster use DEC. So to decrease BX by one use DEC BX. It's quicker than adding 1 too.

Well that's it for this month! Next month I'll explain more about addressing memory and we'll learn how to assemble and link your asm code to QB!

This article was written by: Fling-master - http://www.qbrpgs.com

All site content is © Copyright 2001, HyperRealistic Games. This excludes content submitted by other people.