"It is much better to draw what you see only in memory" - Edgar Degas
Created: 24th October 1999
Last Modified: 27th November 1999
This page explains the INC, DEC and DJNZ instructions and explains the basics of the Video Memory. It also explains the .db and .dw directives.
These two instructions should probably have been introduced earlier in the tutorial.
They are fairly simple - they just INCrement (add one to) or DECrement (decrease by one)
an argument. You can INC or DEC any 8 or 16 bit register or (hl). The general form is
inc whatever or dec whatever.
DJNZ is a 'short-cut' instruction - it is in no way ever necessary but it speeds up
programs and decreases their size. It takes the general form djnz relative
where relative is a relative address (up to 127 bytes away). What it does is DECs the
b register and then if it is non-zero jumps to relative. This is used to give loops.
The .db directive is used to insert a byte of data into the program. This is usually used to set aside small areas of memory inside your program. This can be to use areas of memory as ready-initialised variables (since a copy of your program is made, any changes the program makes to itself at runtime are lost when the program is exited). For example, if you were writing (another) space invaders game, you might have a program like:
#include "ti86asm.inc" .org _asm_exec_ram ; --- Title screen etc. goes here --- LoseALife: ld a,(LivesLeft) dec a ld (LivesLeft),a cp 0 jp z,Dead jp NotDead ; --- More code goes here --- GainSomeAmmo: ld hl,AmmoLeft ld a,(hl) add a,5 ld (hl),a ret ; --- Rest of code --- LivesLeft: .db 5 AmmoLeft: .db 20 YouDieMessage: .db "Ha Ha You Die!",0 .end
Also, if you wanted to, you could insert instructions in hexadecimal format into your
program using the .db directive. .db $c9 is the same as ret,
.db $01,$34,$12 is the same as ld bc,$1234 (when writing
machine code you swap the bytes round, so the LSB (Least Significant Byte) comes first
and the MSB (Most Significant Byte) comes second). The .dw directive just inserts
a word rather than a byte. This is a bit pointless usually, although there are a few
instructions that officially don't exist ("If I told you I'd have to kill you"
instructions, mentioned in no book ever written by Zilog) so you usually have to type
in the machine code by hand like this.
The _puts ROM routine displays a null-terminated string pointed to by hl. This isn't
as complicated as it might sound - hl should contain the address of the first byte
of a string. The null-termainated bit means that the end of a string is indicated by
a character number 0 (not '0', which is character 48, or ' ', which is character 32).
So, if you want to include "Hello" in your program, you could do
.db 72,101,108,108,111,0 (the numbers are the ASCII values of the
characters) or just .db "Hello",0, and the assembler will calculate the
ASCII values for you.
Note: the ti86 doesn't quite use 8 bit ASCII, but the basic characters are the same. Some of the characters have been replaced to allow the inclusion of the Greek and mathematical characters.
Yes, here's the traditional Hello World program. You should know what it does - you saw it in chapter 1.
Source: hello5.asm
Compiled: hello5.86p
#include "ti86asm.inc" .org _asm_exec_ram call _clrScrn ; Clear screen, cursor to top left call _homeup ld b,5 ; Loop counter becomes five LoopStart: ld hl,StartOfText ; hl points to text call _puts ; Display text pointed to by hl call _newline ; New line djnz LoopStart ; Loop back to LoopStart, decrease counter call _pause ; Wait for [ENTER] to be pressed call _clrScrn ; Clear screen, cursor to top left call _homeup ret ; Return from program StartOfText: .db "Hello World",0 .end
You may be wondering how you actually display things on the screen yourself, rather than using ROM calls. Basically, the screen memory goes from addresses $fc00 to $ffff. The equate for $fc00 is something very stupid and no-one uses it.
Each pixel on the screen is one bit in memory. Memory location $fc00 represents the first eight pixels at the top left of the screen going from left to right, $fc01 is the next eight pixels to the right of $fc00 and so on. So:
Note that when writing bit numbers, the least significant bit, written on the right, is bit 0 and the most significant bit, written on the left, is bit 7 when using 8 bit numbers.
Just to go over the last section I'll include a simple program.
The program should turn on the top left pixel on the screen. Note that this is not just a case of setting the value of $fc00 to %10000000 as that would wipe the other seven pixels.
Source: topleft.asm
Compiled: topleft.86p
#include "ti86asm.inc" .org _asm_exec_ram ld hl,$fc00 ; hl points to top left of screen ld a,(hl) ; a contains contents of top left of screen or %10000000 ; turn on first pixel without affecting the others ld (hl),a ; put it back into top left of screen ret ; we've finished .end
And for something a bit more complicated...
The program should draw a line eight pixels long going South West, starting from the top right of the screen. Before looking at the code below try and work this out for yourself. It should be easy. You can use the stack if you want, but the fastest version of the code doesn't need it (he he he...).
Source: swline1.asm
Compiled: swline1.86p
; This Version: ; Size: 20 bytes ; Time: 636 t-states #include "ti86asm.inc" .org _asm_exec_ram ld hl,$fc0f ; hl points to top right ld a,1 ; a contains %00000001 ld b,8 ; line is 8px long ld de,16 ; add 16 to hl to bring down 1 line LoopStart: push af ; save bitmask ld c,(hl) ; get value from screen or c ; a = a or c ld (hl),a ; put back onto screen pop af ; restore bitmask add a,a ; a = a * 2 add hl,de ; hl points to next row djnz LoopStart ; loop back to LoopStart ret ; end of prog .end
In case you're curious, here's the quicker, smaller version. Before someone out there sends me an even faster, smaller way (I already know the one using the shadow registers and the undocumented ixh loads) just check that it doesn't use any completely ridiculous stuff. By the way, this version is 3 bytes smaller and takes 0.03 microseconds less time to run. Stupid, isn't it?
; This Version: ; Size: 17 bytes ; Time: 436 t-states #include "ti86asm.inc" .org _asm_exec_ram ld hl,$fc0f ; hl points to top right ld c,1 ; c contains bitmask ld b,8 ; line is 8px long ld de,16 ; 16 px in a row LoopStart: ld a,(hl) ; a = existing value or c ; a = new value ld (hl),a ; put new value on screen shl c ; c = c << 1 add hl,de ; hl points to next line djnz LoopStart ; loop back to start ret .end
The shl c instruction shifts the c register one bit to the left. This is
like << in c or shl in Pascal. Sorry, all you VB programmers, but your primitive,
illogical language can't do that - you have to write a (lengthy) bit of code to
do it (and then it's ten times slower) :).