Jump. Don't Jump. It's your call." - BT CellNet Advert
Created: 21st October 1999
Last Modified: 6nd December 1999
This page introduces four new instructions - CALL, RET, JP and JR. It also introduces the PC register and explains labels and equates.
When you write a program for the ti86 it is stored as a program variable. When you
run it, the contents of the program are copied to an area of memory starting from
location $d748. However, as some people find this hard to remember (?) some clever
person decided to give it a name: _asm_exec_ram. Now everyone can remember it as it
has a highly memorable and sensible name (???).
You may be wondering how the processor knows where it is in the program - does it have
some mystical powers or something? Nope. It uses another register, called PC (short
for, logically enough, Program Counter). As it is a register we can do things with it,
like changing it or pushing it onto the stack, for example. This effectively means
that we can 'jump' to different areas in memory, like the goto command in
the built-in language.
The CALL instruction performs magic (or at least for now it does). It lets you use lots of ready-written bits of code in the ROM, so if you want to put some text on the screen or do some division (the z80 can't do division as such - you have to write a mini-program to do it) you can just use something that's already written. This also makes your program take up less memory. Less memory used, more games...
What the CALL instruction actually does is pushes the value of PC onto the stack and
then loads PC with whatever address you specify. It takes the general form call
AddressToCall, where AddressToCall is a 16 bit number. So:
call $4a37 ; display null-terminated string pointed to by hl call $4a82 ; clear screen call $4d43 ; pause calculator
We don't know where all the ROM calls start, or exactly what they do - Texas Instruments refuse to release anything remotely useful and it's sort of slow reading the ROM (there's a program that lets you do that).
However, these numbers aren't very memorable, so we gave them names (just like $d748 became _asm_exec_ram). The way we tell the assembler what addresses are represented by which names is through equates. Equates are instructions to the assembler that don't generate any machine code. A sample equate might look like:
_pause equ $4d43
This tells the assembler that every time it encounters _pause it should
replace it with $4d43. However, it would be rather boring to look up and type in
dozens of equates at the start of every program. Fortunately, some of the people at
ACZ have put all the
known equates into a file, so
we can just put #include "ti86asm.inc" at the start of our
program (for those of you that don't write c, this tells the assembler to pretend that
the contents of the file "ti86asm.inc" are where the #include
assembler directive is).
Note that all ROM equates (and RAM equates if they aren't in your program) start with an underscore (_). Also, your assembler is probably case senstitve.
The RET instruction is short for 'return'. It returns from a CALL by POPping the value on the top of the stack into PC (interestingly, the stack goes backwards through memory, so what we call the top is actually the bottom...). Since your program is actually CALLed by the operating system, you also use a RET command to exit your program.
If you put all this together, you can probably see that you must always return from any call and pop anything you push before you exit your program. Otherwise all sorts of nasty things could happen - the calculator's memory could get wiped if you're unlucky (you're always unlucky according to Murphy's Law).
Take a look at the program below and see if you understand it - the comments explain what the various ROM calls do. Try assembling it and running it (click here if you've forgotten how to do it).
Source: dispZ.asm
Compiled: dispZ.86p
#include "ti86asm.inc" call _clrScrn ; Clear the screen xor a ; a = 0 (a = (a xor a), which is always 0) ld (_curRow),a ; memory[_curRow] = a ( = 0) ; This sets the cursor row to 0, the top row ld (_curCol),a ; memory[_curCol] = a (which still = 0) ; This sets the cursor column to 0, the top column ld a,90 ; a = 90 ; 90 is the ASCII value for the character 'Z' call _putc ; Display ASCII character stored in a on the screen ; and advance the cursor call _pause ; Pauses the calculator until you press [ENTER] call _clrScrn ; Clear the screen call _homeup ; Another way of moving the cursor to the top left ret ; Return from the program .end ; Tells the assembler we've finished
You should get something like this:
![]() |
| Program being run |
The JP instruction is short for 'jump', and that's what it does - it jumps to
different bits of code. You could say that jp $1234 is the same as
ld PC,$1234, although the latter isn't actually allowed. It differs from
CALL in that the value of PC isn't pushed onto the stack, so you can't
RET. JP takes the general form jp Address, so you can use
instructions like jp _SomeROMLocation, assuming you've defined
_SomeROMLocation that is...
If you want to JP somewhere in your program (usually if a certain condition is true - this is in the next section) you can work out the address of the line of code using a very complicated (well, just tedious really) formula - count up the number of bytes of code up to where you want to jump to then add it to $d748 to find the real address. Of course some instructions take up one byte, some take up two, some take up three... Alternatively, you can label the location.
A label is really just a name up to 31 characters (depends on the assembler, but most use 31) in length that starts with a letter or an underscore (you also can't use spaces or most symbols in labels). To define a label you just type in the label without any spaces before it, followed by a colon (:). Note that there must always be at least one space before an instruction, but never one before a label. For example:
Source: jp1.asm
Compiled: jp1.86p
#include "ti86asm.inc" .org _asm_exec_ram call _clrScrn ; Clear the screen call _homeup ; Cursor to top left ld a,90 ; a = 90 ; 90 is the ASCII value for the character 'Z' call _putc ; Display ASCII character stored in a on the screen ; and advance the cursor jp AfterDispY ; Jump to label AfterDispY ;The next two lines will never be run! ld a,89 ; a = 89, ASCII character 'Y' call _putc ; Display ASCII character in a ;The rest of this will be run AfterDispY: ; This is label AfterDispY ld a,88 ; a = 88, ASCII character 'X' call _putc ; Display ASCII character in a call _pause ; Pauses the calculator until you press [ENTER] call _clrScrn ; Clear the screen call _homeup ; Another way of moving the cursor to the top left ret ; Return from the program .end ; Tells the assembler we've finished
You can assemble this and run it if you wish - there's a screenshot below.
You should understand all that program except for the .org
_asm_exec_ram bit. All this does is tells the assembler that the program
will start at memory location _asm_exec_ram (the equate represents $d748, so we could
have written .org $d748 or .org
%1101011101001000, although anyone using the latter deserves to be shot, or
possibly even sent to work for Microsoft). We need this because sometimes programs
will be run from other locations.
![]() |
| Program being run |
The JR instruction is very similar to JP, but it is a relative jump. What this means is that rather than giving the absolute address of the jump destination it gives an address relative to the current one. This takes up one less byte in memory, but can only be used to jump to locations up to 127 bytes away.