z80:The Registers and Memory
- 1 Existing tutorials
- 2 Memory
- 3 Registers
- 4 Variables
- 5 Review
- 6 Conclusion
- Sigma's Learn Assembly in 28 Days, Day 3 (scroll past the number bases section).
- Iambian's Some (z80) Assembly Required About Registers
If you still don't understand, read on.
Everything that your calculator does involves memory. Most of your memory usage will be in random-access-memory (RAM), but later on we will discuss the Flash read-only-memory (Flash ROM).
How Memory works
Inside your calculator are tens of thousands of transistors can be changed to either pass a current or prevent a current from passing. Each transistor is a bit. 8 transistors make up a byte. For now, the byte will be the smallest unit we will be working with (actually, we'll be indirectly working with bits).
The z80 processor is a 8-bit processor. This means that it can process 8 bits at a time. However, this means you can only get values up to 255. Since each memory point must have an address, that means you would only be able to access 255 bytes of data. If you've ever checked your calculator's memory menu, you'll know this is not true. So how is your calculator able to access so many memory addresses? It does it by assigning each address 2 bytes. This substantially increases the memory capacity to 65536. Again, check the memory menu, and you'll find that that's enough for the RAM, but what about the number below it? That number can get up to ~1.5 megabytes (roughly 1572864 bytes)! Again, our 16-bit addresses are too small. What if we added another byte to that? that would mean addresses, 16777215. This could work, except that is not how the calculator does it. Instead, that bottom number is the Flash ROM, which is many split up into pages. Each page is 16 kilobytes (16384 bytes), and is accessed by 'switching pages'. Don't worry about this for now. Instead, we'll focus on the top number, the RAM.
Random Access Memory
The RAM is 32 kilobytes in size and can be changed at anytime by the calculator. Please note that some changes are quite harmful to the calculator, and thus should not be done. Besides the point, the calculator can not distinguish data types. Instead, everything to it is a series of bits and bytes. So, it is the job of the programmer to make sure that the data is used meaningfully.
Here's how the calculator handles addresses. Every point in RAM has a 2-byte address. The locations 8000-FFFF are used specifically for RAM, and the areas 0000-7FFF are used to swap flash pages. Remember that .ORG directive from the Hello World program? That tells the compiler where to start counting. The reason the value is 9D93 is because that is the location the OS copies programs to before running them. So, in order to get addressing right, the compiler needs to know what the addresses of the labels are taking into account the move.
The TI-OS can handle 3 basic data types: Integers, Picture Data, and Characters/Strings. However, though it can handle these three data types, it doesn't know the difference between them, so you must be careful in your programming to prevent using the wrong data type for the wrong task.
As you probably know, Integers are numbers without decimals. They can be positive or negative. For the purposes here, integers must fit into 8-bits or 16-bits, or the range <math>[0,255]</math> for 8 bit numbers and <math>[0,65535]</math> for 16 bit numbers.
For right now, all you need to know is that it exists. If you want to know more about picture data, see this page.
Characters and Strings
Characters are those defined by the machine. For every value there is a character associated with it. Now, this may make you wonder how do different machines communicate if you can define characters differently on different machines. The answer is a while ago a bunch of computer engineers got together and put together a table of standard characters and their associated values. This format is known as ASCII, and each character is 1 byte. More recently, because of international use, there has been a need for a 2 byte system. This is known as Unicode, but does not have an internationally recognized standard. Your calculator uses the ASCII table, but strangely enough, it is not the standard ASCII table. Instead, it is TI's version of it.
Strings are just lots of characters put together in consecutive order. However, it is important to identify the beginning/ends of strings. So, here's how it's done:
Null-Terminating Strings Strings that have a null term, or 0 at the end.
.DB "String Data",0
Pre-determined Length Strings Strings where the first byte is the length of the string.
.DB 11,"String Data"
Registers are sections of very expensive RAM inside the CPU that are used to store numbers and rapidly operate on them. At this point, you only need to concern yourself with these 8-bit registers: A, B, C, D, E, F, H, and L.
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 and IY, are 16-bit, and can store a number in the range 0 to 65535.
Although for the most part register use is interchangeable, it is common coding practice to use them in this particular manner:
8 Bit registers
A is also called the "accumulator". It is the primary register for arithmetic operations and accessing memory. B is commonly used as an 8-bit counter. C is used when you want to interface with hardware ports. D is not normally used in its 8-bit form. Instead, it is used in conjuncture with E. E is again, not used in its 8-bit form. F is known as the flags. It is the one register you cannot mess with on the byte level. It's uses will be discussed later in the Flags and Bit-Level Instructions Section. H is another register not normally used in 8-bit form. L is yet another register not normally used in 8-bit form. I is the interrupt vector register. It is used by the calculator in the interrupt 2 mode. R is the refresh register. Although it holds no specific purpose to the OS, it can be used to generate random numbers. IXH The higher (first) byte of the IX register. Note that I is not the higher byte of IX. Combines with IXL to make the IX register. IXL The lower (second) byte of the IX register. When combined with IXH these two registers make up the IX register. IYH Again, this is the higher byte of the IY register. Note that IYH is different from both I and IX. Combines with IYL to make the IY register. IYL The lower byte of the IX register. Combines with IYH to make the IY register.
16 Bit Registers
AF is not normally used because of the F, which is used to store flags. BC is used by instructions and code sections that operate on streams of bytes as a byte counter. Is also used as a 16 bit counter. DE holds the address of a memory location that is a destination. HL The general 16 bit register, it's used pretty much everywhere you use 16 bit registers. It's most common uses are for 16 bit arithmetic and storing the addresses of stuff (strings, pictures, labels, etc.). Note that HL usually holds the original address while DE holds the destination address. PC The program counter. It hold the point in memory that the processor is executing code from. No function can change PC except by actually jumping to a different location in memory. SP The stack pointer. It holds the current address of the top of the stack. IX is called an index register. It's use is similar to HL, but it's use should be limited as it has other purposes, and also runs slower than HL. IY is another index register. It holds the location of the system flags and is used when you want to change a certain flag. For now, we won't do anything to it.
In order to accomplish your goals, you'll need to store data for long term use into the RAM. To do so, you'll need to create variables.
There are two ways to create a variable (although these two ways are practically the same):
Somewhere in your program (usually at the end), 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_list
.DB $0A,$61 .DW $0A61
Why aren't these two similar? Don't they do the same thing (allocate two bytes in memory)? If you tried to access the value of these using a 16 bit register, say HL, you'll end up with:
What? How did that happen? The reason is .DW on compile switches the two values around because the calculator is Little Endian. This means that the value gets loaded into L first, and then into H. So, to make sure that the programmer doesn't get the wrong results, the .DW directive, intended for 16 bit stuff (usually addresses), is used, automatically switching the two bytes around.
Safe Ram areas
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:
variable .EQU AppBackUpScreen
AppBackUpScreen is equal to $9872, so when you store to it, you are really storing to the $9872th byte of the calculator's RAM. To get access to the other 767 bytes of free RAM, you specify an offset, for example:
variable1 .EQU AppBackUpScreen+2 variable2 .EQU variable1+2
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 (variable), HL
You don't have to use just immediate addresses to manipulate your variables, you can actually reference an 8-bit memory location with a 16-bit register in a practice called indirection or indirect access. Indirect access is indicated by enclosing the 16-bit register in parentheses (like immediate addresses). The memory address is then the numerical value of that register. E.g.
LD DE, $4102 LD (DE), A ; Store the value of A to address $4102
Another method of indirect access is the use of the index registers, IX and IY. Both of these registers can have an offset added to them. Please note that this offset is limited to 1 byte, so $00 to $FF or -$7F to $7F. This is especially useful for using system flags, to which IY points to.
set 1,(IY+2) LD (IX+$20),A
Don't worry if you don't understand this, just know that it is there. The use of indirect access will become much more important when we discuss Flags.
Here are some review questions that will help you to understand and remember the information on this page.
1. What would .ORG $0001 do? 2. What would the compiler do if you didn't include the .ORG directive? 3. List the common 8-bit registers and their uses. Then list the common 16-bit registers and their uses. 4. Write some code that allocates data for a calendar. It should have room for 2 bytes/day of the year (365 day, don't do this for a leap year). Do it again using the other way for allocating memory that you did not use the first time. 5. Find the error in this code and fix it.
ld HL,(label1) ;HL should now contain $00A1 label: .db 0,$A1
If you wish, you may check your answers here.
Hopefully, you understood all of that. If not, read it again and maybe you'll get it. This is an extremely important topic so it is recommended that you keep reading it until you understand it.