The Video Memory and DJNZ

"It is much better to draw what you see only in memory" - Edgar Degas

by Ciaran McCreesh
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.

INC and DEC

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

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 and .dw Directives

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

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.

Hello World

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

Video Memory

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:

Diagram of screen memory

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.

Top Left Pixel Program

Just to go over the last section I'll include a simple program.

Program Specification

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.

The Code

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

Diagonal Line Program

And for something a bit more complicated...

Program Specification

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...).

Code - The Version You Probably Got

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
Code - The Quick Version

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) :).