ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННН» є Adam's Assembler Tutorial 1.0 ЗДї є є і є PART V є і ИНСННННННННННННННННННННННННННННННННННННННННННННННННННННННј і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Version : 1.3 Date : 15-03-1996 Contact : blackcat@vale.faroc.com.au http://www.faroc.com.au/~blackcat ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД 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: ADD 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, unsigned char color) { _asm { mov ax, 0A000h 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.