Like you learned in grade school and forgot over summer, in a base ten number, each digit specifies a certain power of 10, and as a consequence you need ten different digits to denote any number. The rightmost digit specifies 100, the second digit specifies 101, the third 102 and so on.
You can, therefore, break down a decimal number, such as 276310, like this (although it does wind up to be redundant):
276310 = (2 x 103) + (7 x 102) + (6 x 101) + (3 x 100)
= (2 x 1000) + (7 x 100) + (6 x 10) + (3 x 1)
= 2000 + 700 + 60 + 3
= 276310
Computers enjoy working with two other bases: binary and hexadecimal. Octal is base-8, and seems to have died out. It was only used by UNIX anyway.
101101012 = (1 x 27) + (0 x 26) + (1 x 25) + (1 x 24) + (0 x 23) + (1 x 22) + (0 x 21) + (1 x 20)
= (1 x 128) + (0 x 64) + (1 x 32) + (1 x 16) + (0 x 8) + (1 x 4) + (0 x 2) + (1 x 1)
= 128 + 32 + 16 + 4 + 1
= 18110
A single binary digit is familiarly called a bit. Eight bits are called a byte. Other combinations you could hear about:
| Name | Size |
|---|---|
| nibble | 4 bits |
| word | 16 bits |
| dword | 32 bits |
| quadword | 64 bits |
We will find ourselves working with, or at the very least referencing the individual bits of a byte or word. The nomenclature:
1A2F16 = (1 x 163) + (10 x 162) + (2 x 161) + (15 x 160)
= (1 x 4096) + (10 x 256) + (2 x 16) + (15 x 1)
= 4096 + 2560 + 32 + 15
= 670310
Hex values have an interesting relationship with binary: take the number 110100112. In hex, this
value is represented as D316, but consider the individual digits:D16 = 11012 316 = 00112Compare these two binary numbers with the original. You should see that one hex digit is equivalent to one nibble. This is what's so great about hexadecimal, converting binary numbers used by the computer into more manageable hex values is a snap.
| Prefix Format | Suffix Format | Base |
|---|---|---|
| %10011011 | 10011011b | Binary |
| $31D0 | 31D0h | Hexadecimal |
| @174 | 174o | Octal |
| 12305 (no prefix) | 12305d | Decimal |
The single-letter registers are 8 bits in size, so they can store any number from 0 to 255. Since this is oftentimes inadequate, they can be combined into four register pairs: AF BC DE HL. These, along with IX, are 16-bit, and can store a number in the range 0 to 65535.
These registers are general purpose, to a point. What I mean by that is that you can usually use whichever register you want, but many times you are forced to, or it's just better to, use a specific one.
The special uses of the registers:
| LD destination, source | Stores the value of source into destination. |
| Destination | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| S o u r c e |
A | B | C | D | E | H | L | BC | DE | HL | (BC) | (DE) | (HL) | (imm16) | |||||||||||||
| A | |||||||||||||||||||||||||||
| B | |||||||||||||||||||||||||||
| C | |||||||||||||||||||||||||||
| D | |||||||||||||||||||||||||||
| E | |||||||||||||||||||||||||||
| H | |||||||||||||||||||||||||||
| L | |||||||||||||||||||||||||||
| BC | |||||||||||||||||||||||||||
| DE | |||||||||||||||||||||||||||
| HL | |||||||||||||||||||||||||||
| (BC) | |||||||||||||||||||||||||||
| (DE) | |||||||||||||||||||||||||||
| (HL) | |||||||||||||||||||||||||||
| (imm16) | |||||||||||||||||||||||||||
| imm8 | |||||||||||||||||||||||||||
| imm16 | |||||||||||||||||||||||||||
You obviously have no clue what difference parentheses make for an operand. You'll see shortly.
Examples:
| LD A, 25 | Stores 25 into register A |
| LD D, B | Stores the value of register B into register D. |
| LD ($8325), A | Stores the value of register A into address $8325 (explained later on). |
Some points that should be made clear:
The two operands for LD cannot both be register pairs. You have to load the registers separately:
; Since we can't do LD DE, HL... LD D, H LD E, LIf you use LD with a number that is too big for the register to hold, you will get an error at assembly time. Storing negative numbers, however, is legal, but the number will get "wrapped" to fit. For example, if you assign -1 to A, it will really hold 255. If you assign -2330 to BC, it will really hold 63206. Adding one plus the maximum value the register will hold gives you the value that will be stored. There is a reason for this phenomenon that will be made clear shortly.
An instruction similar to LD but functionally different, is EX. Despite the fact that it is very particular about its operands, it is a very useful instruction. (90% of the time the registers you want to exchange are HL and DE).
| EX DE, HL | Swaps the value of DE with the value of HL. |
Registers F and AF cannot be used as operands for the LD instruction. Actually, these registers can not be operands for any instruction barring a few.
It turns out that there are many signed numbering schemes, but the only one we're interested in is called the two's complement. When we have a signed value in two's complement, the most significant bit of the number is termed the sign bit and its state determines the sign of the number. The existence of the sign bit naturally imposes a restriction on the number of bits a number may be composed of. With this, the amount of bits at our disposal to represent the number is reduced by one; for a string of eight bits, we can have a numeric range of -128 to +127. For a string of sixteen, it's -32, 768 to 32, 767, etc.
As to what bearing the state of the sign bit has on the value, it is this: if the sign bit is clear, the value is a positive quantity and is stored normally, as if it were an unsigned number. If the sign bit is set, the value is negative and is stored in two's complement format. To convert a postive number to its negative counterpart, you have two methods, either of which you can choose based on convenience.
%10000000 -128
%01111111 Invert all bits
%10000000 Add one (=-128)
Of course -(-128) isn't -128, but the value +128 cannot be represented in two's complement with just eight bits, so the smallest negative value can never be negated.There is an instruction to automate two's complement:
| NEG | Calculates the two's complement of the accumulator. |
unsigned signed
%00110010 5 5
+ %11001110 + 206 + -5
%1 00000000 256 0 (Disqualify ninth bit)
This phenomenon was not lost on the makers of the Z80. You could use the same hardware for adding signed numbers as unsigned numbers only with two's complement, and less hardware means a cheaper chip (true, nowadays you can get a fistful of Z80s for fifty cents, but back in 1975 it was a big deal, just look at the 6502).
The TI-83 Plus's RAM consists of 32 kilobytes, and each byte is distinguishable from its myriad bretheren by a unique number, called an address, which is a number from $8000 to $FFFF (addresses $0000 to $7FFF are used for the Flash ROM). Now, you must realize that all data on the calculator, from numbers to text to pictures, is really just an endless series of numbers to the computer (in fact, it isn't even that), and programs are no exception. This is related to an important point of computer science, and I want you to make it your mantra: "Data is whatever you define it to be".When you run a program, the calculator takes the series of numbers that makes up the program, transfers it to some other place in RAM (to address $9D95 as it happens), and starts wading through it, sending each number it comes across to the processor. When you assemble a program, you are converting all those instructions into numbers. An instruction could take one to four bytes, and a ROM call takes up three bytes.
To assist in keeping track, TASM uses a location counter. This is the current address data will be located at when the program runs. The .ORG directive sets the location counter to a certain value at that point in the program source. As the program is compiled, the location counter is incremented for each byte of machine code generated.
The location counter's value can be used in programs. It is represented by $ or *, $ being preferred, mainly because not very many people know about * :D. TASM doesn't have any problem with the location counter confliciting with hexadecimalitude, in case you were wondering.
symbol = literal
symbol .EQU literal
After defining any equate, the symbol may be used anywhere its literal would be acceptable.
Lable: ; L.C. = $9452 LD HL, Lable ; HL = $9452
.MODULE module_1 Code here ; This code is in module module_1 .MODULE module_2 Code here ; This code is in module module_2 ; (ain't I just the king of originality? :-)A local label name (which begins with an underscore) can be defined multiple times as long as each new definition occurs in a different module.
.ORG $1000 .MODULE x _local: LD HL, _local ; HL equals $1000 .MODULE y _local .EQU $5000 LD HL, _local ; HL equals $5000
#define symbol literalMacros can be used as replacements for equates, and even to insert equates. But what make macros particularly interesting is that they can be parameterized to create similar, but different, pieces of code.
#define move(src, dest) LD dest, src
For this example macro that could be nice if you are used to Motorola's syntax, the parameters src and dest are matched to the src and dest in the text constant. An example invocation
move(A, B)
Would be replaced with
LD B, AYou can have any number of parameters as long as every parameter be used. To have a macro that is many lines long, use #defcont. Note the backslashes, they are mandatory if you want assembleable code.
#define add_sto(reg, addr) LD A, (addr)
#defcont \ ADD A, reg
#defcont \ LD (addr), A
There are two ways to create a "variable" (although these two ways are practically the same):
At the end of the program, create a label that will be used to access this variable. Immediately after the
label, allocate memory for the variable using .DB or .DW
(you could instead use .BYTE and .WORD).
.DB value_list .DW value_listThe value_list is a series of one or optionally more values, each separated by a comma. The only difference between the two directives is that .DB formats each value as a byte, but .DW formats each value as a word. .DB can be used to replace .DW except for when one of the values is a 16-bit manifest constant.
Also remember that .DB and .DW don't intristically create variables, they just insert bytes into your program. If you know the hex codes, you can do machine language and prove yourself to be a wycked üβ3r1337 h4x0r.
; The machine code for LD B, 6 LD A, B ADD A, H LD B, A .DB $0E, $06, $78, $84, $67So say you had a variable:
Var_X: .DW 1000Then you access the data there using parentheses around the label name: (Var_X). See the chart earlier for all LD forms for which this is legal.
The second way to create a variable is to find some free RAM not being used by the calculator. There are 768 bytes of RAM not used by the system at AppBackUpScreen. And if this isn't enough, you can use SaveSScreen (another 768 bytes), as long as the Automatic Power Down doesn't trigger. There are a couple other places, but I can't possibly see how you'd need more than 1536 bytes of scrap RAM, so never mind about them.
To create a variable in this way, you use our old pal .EQU, like this:
trash .EQU AppBackUpScreenAppBackUpScreen is equal to 39026 (it's moronic to communicate addresses in anything other than hexadecimal, I'm just playing around with ya :-), so when you store to stuff, you are really storing to the 39027th byte of the calculator's RAM. To get access to the other 767 bytes of free RAM, you specify an offset, for example:
garbage .EQU AppBackUpScreen+4 refuse .EQU AppBackUpScreen+8The effect is that during assembly, TASM takes the value of AppBackUpScreen (39026) and adds 4 to it (in the case of variable garbage, resulting in 39030). So garbage is referencing the 39031st byte of RAM. It's a similar deal with refuse.
To store to a variable, you use the A register for one-byte numbers. For two-byte numbers, any two-byte register is fine, but HL is usually the best choice.
LD HL, 5611 LD (garbage), HL
LD DE, $4102
LD (DE), A ; Store the value of A to address $4102
See the table from before to see all the legal methods.But that's not all! You can use IX for indirection too, but there is a pleasant twist. You can add a constant value (called an offset or displacement) in the range -128 to +127 to the register's value:
LD IX, $8000
LD (IX + $26), 196 ; Store 196 to address $8026