Modes of addressing memory in low level languages
Introduction
Some programming tasks require a programmer to program in a way that reflects the CPU design. The programmer may need, for example, to directly manipulate memory addresses or the CPU's registers. This is often true when a device driver for a piece of hardware is being written or when a translator is being written for a high level language. It would be appropriate in these cases to use a language that has instructions designed to easily carry out these tasks. High-level languages generally would not be the first choice, because their focus is more on solving application-based problems. The first choice would be assembly languages. These types of languages have instruction sets that reflect the way the CPU carries out its instructions, including instructions that allow you to manipulate a CPU's registers. Their instructions are very close to machine code itself. These languages use mnemonics, which are reasonably easily remembered codes for instructions. Examples include:
-
- DIV 18 ---- Divide the contents of the accumulator by 18 and store the result in the accumulator.
- LDA (3000) ---- Get the contents of address 3000 and move them to the accumulator.
- SUB (5000) ---- Get the contents of address 5000 and subtract them from the accumulator. Store the result in the accumulator.
- MOV B @100 ---- Get an address held in address 100. Go to that address and get the contents. Move them into accumulator B.
Note that decimal numbers are used in my examples throughout this section for simplification. Using hex might be an easier option! Can you think why? Note that in the above examples, each instruction has an instruction part and an address part to it. For example, in the instruction LDA (3000), the LDA is the instruction part of the instruction and the 3000 is the address part of the instruction. The address part refers to an address where the CPU needs to look in RAM to find some data. This is a very common format for assembler instructions (although a few instructions differ from this format).
-
- The instruction part of an assembler instruction is usually known as the operation code, or opcode for short.
- The address part of the instruction is usually known as the operand.
We can represent this general format of most assembler instructions like this: <OPCODE><OPERAND> Now you might think that to refer to a piece of data in RAM, you simply give the address in RAM where the data can be found. Unfortunately, it is not quite as simple as that! There are a number of different ways that an assembler language can refer to any particular RAM address. There are different ways because each method has some particular strengths in a particular set of circumstances. The four methods we will discuss are immediate addressing, direct addressing, indirect addressing and indexed addressing.
Immediate addressing
We have said that an assembler instruction usually follows the format <OPCODE><OPERAND>, with the operand containing an address. In immediate addressing, the operand part of the instruction doesn’t contain an address! It contains a piece of actual data instead. The CPU doesn’t need to waste time by going to a RAM address and fetching a piece of data because it is already there, as part of the instruction! This is a very useful form of addressing if you need to use a constant. For example, if you needed to divide a number by the number of months in a year, then you could get and use the number 12 using immediate addressing, because the number of months in a year will never change. Consider the mnemonic : DIV 18. We know that this is an assembler instruction that uses immediate addressing because of the form it takes. It has an opcode followed simply by the decimal number 18 (use the bit pattern for the decimal number 18) and nothing else. If I had written the instruction as DIV #18, I would need to use the bit pattern for the hex number 18.
Direct addressing (also known as absolute addressing)
This is so called because the instruction being worked on, for example, ADD, SUB and so on refers directly to the memory location where the data for the instruction can be found! It sounds a mouthful, but you have already seen some examples of direct addressing.
We saw the instruction SUB (5000) earlier. The instruction SUB contains a reference to the RAM memory location 5000 where the data it needs for the instruction can be found. Note the difference between SUB 5000 and SUB (5000). SUB 5000 is an example of immediate addressing whereas SUB (5000) is an example of direct addressing. The brackets are important!
Using direct addressing, you can write programs that have instructions that refer to data found in specific memory locations. In the instruction, you have actually said where the data is to be found. The instruction SUB (5000) tells you to go to memory location 5000 to find the data to use in the SUB instruction. Direct addressing is simple to code compared to some other methods such as indirect addressing and can be useful technique. For example, you may need to access a constantly changing running total. Direct addressing would allow you to do this.
Indirect addressing
Direct addressing is fine as long as you know what memory locations contain the data you need. But suppose you need to use a library program that's in memory. How can you access that program, if you don't know where in RAM the loader has put it?
Indirect addressing is one way to solve this kind of problem. To see how indirect addressing works, follow this example. Imagine the instruction: MOV A, @11000 (The @ symbol is being used simply to show we are using indirect addressing).
Also, assume that RAM location 11000 contains the value 23000, and RAM location 23000 contains the value 5000. Memory location 5000 holds 32000.

When the CPU carries out this instruction, it:
-
- Goes to memory location 11000.
- Gets the value contained in that location, i.e. 23000.
- Goes to memory location 23000.
- Gets the value contained in that memory location, i.e. 5000.
- Goes to memory location 5000.
- In memory location 5000, it finds the value 32000. This is loaded into the accumulator.
Why indirect addressing is very important
Consider this problem. A programmer wants to use a pre-written library procedure. They want to refer to it in their own program. The problem is, they don’t know where in memory the loader will actually put the procedure. For example, a programmer is writing a program that needs to call a library routine called PrintDocument. When the library program is compiled, however, the loader, which is responsible for putting the library program in RAM, could put it anywhere! And if our programmer doesn't know where it is, how can she jump to it in her program? What she does know, however, is that there is a special area of RAM that holds memory locations (called vectors) that point to where library routines will be. She has the Specification of these vectors and on this Specification will be the vector for the PrintDocument program. So in her program, she can now use indirect addressing to get access to the PrintDocument program simply by using the vector address. When the library program is compiled, it will be the job of the 'loader' to keep the addresses held in the vectors up-to-date, so that programs that jump to the vectors can be re-directed to the correct place in RAM.
An analogy of indirect addressing
Each day when you arrive for school, you must go to a room for your first lesson. The problem is that your timetable is not fixed! The school puts you in a different room every single day. Assume that it does this because the teachers only decide what exciting activities you will be doing (and what facilities you need) the night before, after you have gone home. Now if the teachers don’t decide this until the previous evening, and you never know from one day to the next which room you’ll be in, how will you find out where to go when you arrive in the morning? It is very unlikely that you will see the teacher before lessons start! What you and your teacher can do is to use a notice board. Each evening, once the decision has been made about which classroom you will be in, the teacher puts a notice on the notice board telling which room to go to. In the morning, although you don’t know which room to go to straightaway, you know a place where you can go to find out i.e. the notice board. This is a wonderful system because by using the notice board, the teacher has complete and total flexibility about which room to use. You, of course, will never miss a lesson because, even though you don’t ever know where your first lesson is when you arrive at school, you know where to go to find out. The notice board is like a vector address. If you go to the ‘vector address’, you will find some information about where your first lesson is.
Indexed addressing
Indexed addressing is very often used to access numbers held in arrays. The programmer needs to set up an 'index'. This is simply a variable that they can manipulate. To use this addressing method, you state a base address. You then add to it the value held in the index. This gives you a new address. You then go to this address to get the data! For example, suppose you have a simple array that starts at address 1001. You can set the base address to 1000. Then if you want the data from the sixth location, for example, you add 6 (the index) to the base address to get 1006 and then jump to that location to get the data. If you want the data from the ninth location in the array, then you add 9 to the base address, and jump to location 1009. What is the point of this addressing system? It is a relatively quick and easy job to alter the index register. You can therefore access large chunks of data easily. For example, if you had to get all the values held in an array, you would get the base address, set up an index and then get the data from the address pointed to by (base address + index). Of course, you would need to set up a loop and increment the index in each loop until the end of the file is reached, like this:
GET BASE address
MAKE INDEX=1
WHILE the index does not point to the end of the array
ADD BASE to INDEX
READ data
INCREMENT INDEX
ENDWHILE
This method is also very convenient if you want to store your array in different places in RAM. The only thing that you would actually need to change is the base address. For example, if you now want to run the program with the data starting in 2001 and not 1001, you just need to change the base address in the program. All the other addresses in the array are worked out relative to the base address. The advantage of this is that you don't have to make lots of changes to your program - only the base address needs to be changed.