In the real world, the most useful arithmetic involves numbers that are not all integral, i.e. have a fractional part. With suitable rules, these too can be represented using a binary pattern.
Conceptually, the simplest method is to choose a place for the binary point. Bits to the left are the whole number part and bits to the right are the fractional part. Thus, if the point is taken to be between bit 4 and bit 3 in an 8-bit pattern: 01011101, it is interpreted as:
in a completely analogous manner to the digits in a decimal fraction. Since, in fixing the position of the point, we are in effect, multiplying integers by a scaling factor (in this case 2-4) positive and negative fractional numbers can be represented in twos complement form, and the number of significant figures depends on the value of the number being represented.
Floating point
Floating point representation has two essential groups of bits, one of which, the mantissa, contains a fixed-point binary fraction and the other, the exponent, contains an integer used to calculate the scale factor. There are many ways in which these two parts can be coded, as a 2's complement fraction and an integer, respectively, for example.
The number of bits allocated to each group depends on the computer, its word length and use. The number of bits in the mantissa determines the number of significant figures, the number in the exponent, the range of numbers that can be represented.
To indicate the principle, consider an 8-bit group in which the 3 left most bits are the exponent and the 5 right most, the mantissa, both in 2's complement notation. Thus the pattern:
has '101' (-3) for the exponent and '11011' (-0.15625) for the fraction which combine to give:
An unrealistic (but easy to follow) example of how 8 bits could be used to represent a floating point number might be:
Which is the same as saying: -0.6875/2 = -0.34375.
The three digits used for the exponent can have 8 possible values: 0 - 8, but if we subtract 4 from this number we can represent numbers both larger and smaller than the binary fraction.
More sensibly, a 32-bit representation might be used where 8 bits could be used for the exponent, 23 bits for the binary fraction, and the remaining bit is used for the sign.
2 Flags
Computers store numbers in registers. If our register can only store a byte, then the largest value that it can remember is 255. What happens if our result is greater than 255? Similarly what happens if we subtract a large number from a small number? We need a mechanism to indicate that the limited capability of our calculator store has resulted in an incorrect answer, because there was either a CARRY or a BORROW.
This is indicated using a special 1-bit store called THE CARRY FLAG. If any arithmetic or logical operation generates a ‘carry’, then the carry flag is SET, else it is RESET.
Examples of logical operations that can cause a carry are the SHIFT and ROTATE operations that are discussed later.
All computers have a CARRY FLAG. The other flag that you will always find is known as the ZERO FLAG. It is SET if the result of the last operation was a zero, else it is reset.
There are other types of FLAG that will come across, such as SIGN, HALF-CARRY, PARITYand INTERRUPT. These are usually stored in a special register called the CONDITION CODE REGISTER (CCR) or just known simply as THE FLAGS.
The half carry indicates that a carry has occurred between bits 3 & 4 of the accumulator. It used for BCD arithmetic.
The sign flag indicates if the last arithmetic operation resulted in a –ve result (i.e. the sign bit is set to a 1).
The interrupt flag indicates whether or interrupts are active. If it is set to a 1 then the Interrupt Mask is SET, so interrupts are disabled
The parity flag is set if the parity of the accumulator is even.
3 My First Computer
The core of a computer is a component known as the Arithmetic & Logic Unit, or ALU. It is able to perform a range of arithmetic and logical operations, such as ADD, SUBTRACT, AND, OR etc.
T
he ALU shown here can perform one of 8 different operations. The Accumulator is always a source of data, and the destination of the result. The other source of data (if required) will be from any one of the other registers available in the computer.
How do we decide which instruction to perform? We could use a decoder to select the operation.
W
e need 3 control lines to select one of 8 different arithmetic or logical operations, and 2 control lines to select the other source of data for that operation.
Control 3
|
Control 4
|
Register
|
0
|
0
|
0
|
1
|
0
|
1
|
0
|
1
|
2
|
1
|
1
|
3
|
So how do we ADD REGISTER 2 to the ACCUMULATOR?
To select ADD we set control lines 0,1 & 2 to 000, and control lines 3 & 4 to 01. When the binary pattern 00001 is put onto the control lines then contents of register 2 will be added to the accumulator – that is the way the circuit is designed.
3.1 Exercises
What bit pattern do we use to perform the following operations :
SUBTRACT REGISTER 1 FROM THE ACCUMULATOR
AND REGISTER 3 WITH THE ACCUMULATOR
OR REGISTER 0 WITH THE ACCUMULATOR
3.2 Programmes
We have now seen that if we apply a binary pattern to the control lines of our computer it performs a known operation. The types of operation that we can perform are very simple – the above sequence (exercise 8.1 and section 8) consisted of a sequence of 4 operations.
ADD REGISTER 2 TO THE ACCUMULATOR
SUBTRACT REGISTER 1 FROM THE ACCUMULATOR
AND REGISTER 3 WITH THE ACCUMULATOR
OR REGISTER 0 WITH THE ACCUMULATOR
This sequence is known as a PROGRAMME. A programme is a sequence of 1’s and 0’s which when applied to the control lines of a computer will force to perform a known sequence of operations. This is the link between the ‘programmer’ and the engineers that designed the computer.
Binary patterns are recognised by our computer and are known as MACHINE CODE. They are usually represented in HEX as opposed to binary. There is a limit to the number of control lines that are found in a computer. A typical microcontroller would only have 8 control lines, which means that there a maximum of 256 possible machine code instructions. These are known collectively as the computer’s INSTRUCTION SET.
Human beings prefer words to numbers, so for each machine code there is a mnemonic code that represents that instruction. For example our 4-line programme could be expressed as -
ADD 2
SUB 1
AND 3
OR 0
This type of programming is known as ASSEMBLER CODE or ASSEMBLER LANGUAGE. There is one assembler instruction for each available machine code. Note that each instruction has two parts, one part is the actual operation itself, known as the OPCODE (e.g. ADD), the other part is the memory location that contains the data that is operated upon, or the OPERAND.
We will see later that there are different ways of selecting a unique memory location, known as ADDRESSING MODES. For now we have defined only two types of ADDRESS, register addressing and inherent addressing. Register Addressing selects a unique Register (in this case 0,1,2 or 3). Inherent means that the OPCODE itself
refers to a specific address, such as INC and DEC, which always operate on the accumulator.
4 Memory
Computers require memory to store programmes and data. But what do we need to store in the memory? The simplest partition of the memory is into 3 parts – CODE, DATA & STACK.
The code partition holds our programmes. The data partition holds our variables. The stack partition is used to manage programme flow, parameter passing and interrupts.
Memory comes in a hierarchy, ranging from ‘slow, big and low-cost’ to ‘fast, small and high-cost’.
The picture below show the simplest partition of our memory.
C
ode is usually placed at the bottom of memory, data dome next, then we have the ‘stack’.
4.1 How Do We Manage Programme Flow
First reconsider our 4-line assembler programme from earlier
ADD 2
SUB 1
AND 3
OR 0
Each of these commands will need to be stored in consecutive locations in our CODE SEGMENT.
S
o how does our computer know where to find the next opcode? This is achieved using a special register known as the INSTRUCTION POINTER (IP) or PROGRAMME COUNTER (PC).
Let us review our picture – We need to add a DATA BUS that is used to move opcodes and data from memory to the CPU, and results back into memory. We also need an ADDRESS BUS that is used by the CPU to uniquely select a specific memory location, either to access data or to obtain the next opcode.
This discussion assumes that we are using a computer with an 8-bit data bus and a 16-bit address bus. A 16-bit address bud allows us access to 2
16 (65536) memory locations. An 8-bit data bus means that our computer can support a maximum of 256 op-codes.
How does our computer manage the address bus? The programme counter is used to point to the next op-code, so we cannot use that to point to data. We need another pointer to select a data memory location; one method is to employ a special 16-bit register known as the INDEX REGISTER.
Our instruction pointer is automatically set to 0000H when our computer powers up. So the first op-code that will be e
xecuted is always at the bottom of memory. Our computer then enters the ‘instruction-fetch-execute’ cycle. Each op-code is broken down into a series of steps, which can be generalised as -
-
FETCH THE OP-CODE
-
FETCH THE OPERANDS
-
EXECUTE THE OP-CODES
-
STORE THE RESULT
So the op-code is not the lowest layer of our computer’s command structure, there is a lower layer that breaks down each op-code into a series of ‘micro-instructions’ or ‘micro-code’.
When each op-code is ‘fetched’ from the code segment, the instruction pointer is automatically incremented to point to the next available code segment memory location. This may be an extension to the current op-code (e.g. data or an address) or it may the next op-code. When our op-code has completed execution, the instruction pointer now ‘addresses’ the next op-code.
As our instruction pointer is a register, we can either make it increment as already discussed, or we could reload it with a totally new address. Consider the next piece of code –
In this instance, the op-code at location 2 loads the instruction pointer with the operand 0006. So our next instruction will be located at address 6 – it is the equivalent of the BASIC instruction GOTO, and is the means by which we can implement a branch in our programme flow.
4.2 Conditional Branching
Overwriting the instruction pointer using a JMP instruction. This type of branch is known as UNCONDITIONAL, as it will always happen. The really powerful instructions are those that combine the contents of the FLAGS with the branch instruction, these are the CONDITIONAL BRANCH op-codes.
Consider just two of the flags, CARRY and ZERO. The carry flag is set if the last arithmetic or logical operation generated a carry, the zero flag is set if the result was a zero.
All computers support the following four conditional instructions, in one form or another –
JZ Jump if zero
JNZ Jump if not zero
JC Jump if carry
JNC Jump if not carry
It is the existence of these instructions that gives a computer the ability to take decisions. All high level constructs, such as IF THEN ELSE, DO WHILE, FOR etc. Are compiled down to one of these types of op-code.
4.3 Subroutines
Another form of branching that we need to support are subroutine calls. At a low level you would use commands such as –
CALL SUBROUTINE ; RUN ‘FUNCTION’ USING A 16-BIT ABSOLUTE ADDRESS
JSR SUBROUTINE ; RUN ‘FUNCTION’ USING A 16-BIT ABSOLUTE ADDRESS
BSR SUBROUTINE ; RUN ‘FUNCTION’ USING A 8-BIT RELATIVE ADDRESS
The CPU saves the current contents of Instruction Pointer, and then loads it with the address of the first line of the subroutine. The very last instruction of the subroutine will something like RET or RTS. When this opcode is executed the contents of the instruction pointer are restored, so that execution continues from the point that we called the subroutine from.
The ‘return address’ is temporarily stored in the STACK SEGMENT. Access to the stack is via a special purpose pointer register known as the STACK POINTER. When a return address is placed onto the stack it is said to be ‘pushed’ – the stack pointer automatically decrements after each push operation, such that it points to the next free entry.
Other uses for the stack segment include passing parameters to subroutines, receiving returned values from a subroutine, temporary storage of local variables and management of interrupts.
4.4 Speed Of Access – Backup, Cache and Pipelines
At the time of writing the typical access time for MAIN MEMORY is 60ns. Our main memory holds our code, data and stack segments. However it is only part of our memory hierarchy. When we are not using programmes or data they can be stored on large, slow BACKUP MEMORY devices such as hard disks, CD-ROMs, floppy disks and ZIP-Drives; we only need to load them into our main memory when we want to use them.
Once our code and data are loaded into main memory, and we are allocated a stack segment we can start executing the programme.
60ns sounds fast, but as today’s programmes
consist of megabytes of code, and data storage for images can be vast, it all adds up to a lot of time. To speed things up we use another type of memory called THE CACHE. This is very high-speed memory that is located close to our CPU, and runs at between 2ns to 25ns depending upon how it is organised.
Main memory is usually made from STATIC RAM, similar to the D Type latches discussed earlier. Once data is written to them, it is stored for as long as power is applied to the memory device. Cache is usually made from DYNAMIC RAM; each memory cell contains far fewer active devices than dynamic ram memory, which is why they operate faster. However the data that is stored on them decays (i.e. the data is lost), so they have top be regularly REFRESHED, to restore the stored data.
Cache is used to speed up data segment access. If a data variable if used, then it is probable that it will be used again very soon; so instead of putting it back into main memory a copy is retained in the cache. In this way the CPU can access the most recently used data very quickly. Cache locations are allocated dynamically, as they are required. When the cache is full, the oldest data is put back into main memory freeing up a new location.
What about CODE? If we have just executed an op-code at location n, then the next instruction is probably going to be at location n+1. Instead of waiting for instruction n to finish, we might as well get the op-code at location n+1 whilst the CPU is executing instruction n. This is known as PIPELINING. The execution part of the CPUs control logic fetches more than just the next instruction, it fetches a batch of sequential instructions, ready for execution. Some more advanced devices look out for branch operations, and fill up two pipelines ready with both possible routes, so that it is always ready to deliver the next op-code in the shortest time possible.
5 MEMORY MANAGEMENT
Memory Management Units are responsible for allocating available main memory space to functions. This is a brief overview of how this task is achieved.
5.1
Virtual Memory
http://computer.howstuffworks.com/virtual-memory.htm
The above link gives a good description of virtual memory, with better pictures than mine. As we have already discussed, modern programmes need a lot of memory – and if you start executing a lot of programmes then you are liable to run out of main memory to keep them in. Virtual memory overcomes this problem. The Virtual Memory Manager looks for available RAM to store a new programme in. If there is not enough space then it looks for a block of RAM that has not been used for some time (i.e. a programme that is ‘running’ but not actually doing anything at that moment of time), and copies that programme’s RAM onto a storage media such as a hard disk. This frees up main memory for the new task. When the dormant programme starts up again, its’ ‘ram image’ is restored to main memory.
5. 2 Segmentation
This is a totally different process that is used to allocate the available main memory RAM to a new task.
When we compile a programme it creates an executable that requires 3 memory segments, CODE, DATA & STACK. These segments are referenced using pointers, such as the INSTRUCTION POINTER, the INDEX REGISTER and the STACK POINTER. Let us assume that these pointers are 16-bits wide; then each segment consists of only 64k bytes.
However our main memory is much larger than this, a MEGABYTE has 1048576 memory locations. Segmentation is a technique that allows us to locate our programme segments anywhere within the main memory.
Our programme address space is only 16-bits wide – which means that all memory pointers have a range of 0000 to FFFF. However if we have a megabyte to play with, our real memory space has a range of 000000 to FFFFFF, which is a 24-bit address.
In effect we have 256 ‘blocks’ of 64k each available. Our programme requires 3 of these, for code, data and stack. The memory management unit allocates 3 free blocks for this new task. The simplest way to do this is to have a set of segment pointers, each of which are 8-bits, that are used to ‘offset’ our programmes blocks into the available main memory. This is known as the EFFECTIVE ADDRESS, as it is the actual location in main memory where will find our information.
An effective address is obtained by combining the programme's pointer information with the segmentation offset.
EFFECTIVE ADDRESS = POINTER (lower 16-bits) + SEGMENT OFFSET (upper 8-bits)
With the above method CODE, DATA and STACK can never overlap.
Another technique, as used in PCs, is to allow an overlap between the pointer address and the segment offset. In this case the pointer gives us the lower 16-bits, and the segment gives us bits 4 through to 20. This technique provides a more efficient use of main memory, as unused gaps are removed – however it can cause problems as poorly written programmes may allow data
to be overwritten with code, and visa-versa.
6 ADDRESSING MODES
So far we have only talked about using memory pointers, such as the Stack Pointer, Instruction Pointer and Index Register. The Index Register allows us to point to any data location of our choice, but it requires us to preload the Index register with an address. There are more direct forms of addressing data that can be used.
REGISTER
IMMEDIATE
DIRECT
INDIRECT
INDEXED
6.1 Register Addressing
All CPU's contain some internal registers, which act like local fast data stores. For example an 8086 type CPU contains 4 'general purpose' registers called AX,BX,CX and DX. Programmes can access these registers as an integral part of their op-codes,
e.g. MOV AX,BX will move the contents of register BX into register AX.
You are unlikely to ever make use of REGISTER ADDRESSING unless you are prepared to abandon your compiler and write your code in assembler. Some C compilers allow you force a variable to be stored in a REGISTER by a slight modification to the variable declaration. e.g. register unsigned int name - will force the compiler to try to reserve a register for the variable 'name' to be stored in a register. You are not advised to use this facility.
6.2 Immediate Addressing
IMMEDIATE ADDRESSING means that the number that we are loading is part of the instruction itself, and is not found in the data memory.
e.g. MOV AX,#334 - will load the AX register with the number 344.
The following two lines perform the same operation in C
int my_variable ; // declare an integer Variable called 'my_variable'
my_variable = 344 ; // store the number 344 into my_variable
With immediate addressing the data is fixed in the code, and is therefore a CONSTANT. In the example given above the number 344 is the constant.
6.3 Direct Addressing
DIRECT ADDRESSING means that the opcode includes the address of the variable that we wish to load. e.g. MOV AX,my_variable - the accumulator AX is loaded with the contents of my_variable.
Or in C
int my_variable ; // declare an integer variable called my_variable
int another_variable ; // declare another variable
my_variable = another_variable ; // whatever was stored in 'another_variable' is // copied into 'my_variable'
You may have now realised that variable names are really just alternative ways of using an absolute memory address. When the compiler 'compiles' our source code, it first allocates a unique memory address for each and every variable that is declared. The machine code accesses memory using numeric addresses, but we access memory by using the names that we declare in our source programmes.
With direct addressing our data address is fixed, but the data value itself can vary.
6.4 Indirect & Indexed Addressing
INDIRECT ADDRESSING means that the address of the variable that we wish to address is not
included in the op-code at all, instead we are given the address of a different variable that contains the address the actual variable that we wish to access.
At a low level this can be achieved by means of either an INDEX or a POINTER register. These are special registers in the architecture of the computer that are specifically designed for use as indirect registers.
In an 8086 one these registers is called BP or base pointer. If we load BP with a number, then that number can then be used as the address of a variable
e.g.
MOV BP,#344 ; Load the base pointer with the number 344
MOV AX,[BP] ; Load the AX register with the contents of the memory location
; pointed to by BP
When an internal register is used as a pointer it is known as INDEXED ADDRESSING.
Alternatively we can use another memory location as our data pointer.
e.g.
MOV 178,#344 ; Load memory location 178 with the number 344
MOV AX,[178] ; Load the AX register with the contents of the memory location
; pointed to by the contents of memory location 178
When an external memory location is used as a pointer it is known as INDIRECT ADDRESSING.
In C this is achieved as follows -
unsigned int my_variable ; // declare a variable for us to play with
unsigned int *pointer ; // declare a pointer to an unsigned integer
So we have a variable to use, and we have a pointer as well. Remember that when we start our programme the pointer DOES NOT HAVE A VALUE, as yet it does not know what to point at.
I will now introduce another C concept, the 'address' symbol, which is a '&'. The & operator returns the address of the variable that it is applied to.
my_variable = 344 ; // load 344 into my_variable
pointer = &my_variable ; // load pointer with the address of my_variable
We can now either directly address my_variable using its' name 'my_variable', or we can indirectly address the identical memory location using the pointer. When a pointer is used we use the * symbol to indicate that we wish to access the variable that the pointer 'points to'.
IMMEDIATE ADDRESSING -
my_variable = 344 ; // the source data is the constant 344
DIRECT ADDRESSING -
another_variable = 344 ;
my_variable = another_variable ; // the source data is the contents of address // 'another_variable'
INDIRECT ADDRESSING
pointer = &another_variable ;
another _variable = 344 ;
my_variable = *another_variable ; // the source data is the contents of the // memory location pointed to by the contents of pointer