Microelectronics: SOFTWARE -- Instructions



Home | Forum | DAQ Fundamentals | DAQ Hardware | DAQ Software

Input Devices
| Data Loggers + Recorders | Books | Links + Resources


AMAZON multi-meters discounts AMAZON oscilloscope discounts


The CPU reads a program in a series of fetch-execute cycles in which it reads a machine code instruction and then obeys it. Writing programs in machine code is difficult, but easier if we use an assembler program and write it in mnemonics. The assembler turns the mnemonics into machine code. A program written in assembler can have a title and comments added to it, to make it even easier to understand.

A CPU can do many different things, but it does nothing unless it is told to. It needs to be issued with instructions. The instructions are issued to it in the form of a program, stored in memory. The instructions that a given CPU can understand and obey are its instruction set.

Different types of CPU have a different instruction set; that is, they have a particular collection of instructions suited to the architecture of the CPU. Some kinds of CPU have a large instruction set with several hundred instructions in it (CISC). Others work on a much smaller set, usually fewer than a hundred (RISC). Usually all the members of a family of CPUs have the same instruction set. Some members of the family, especially the newer ones, may have a few additional instructions in their set.

The CPU obtains its instructions by going to an address in memory and reading the instructions stored there. There are very fast CPUs that read more than one at a time but most CPUs read one instruction at a time and act on it before fetching the next one. This is the type of CPU we describe in this SECTION. We shall also confine our descriptions to systems in which the data is operated on as single bytes.

Fetch-execute cycle

When it is operating, a CPU repeatedly goes through a cycle known as the fetch-execute cycle. As its name implies, this cycle has two stages:

• The CPU fetches an instruction from memory.

• The CPU executes it, that is, it obeys the instruction.

The cycles are repeated continuously as the CPU works its way from the beginning of the program to the end.

The instruction is a byte of data stored in memory. Reading a byte from memory takes place in ten stages:

• The address of the byte to be read is transferred along the internal bus of the CPU to the address bus register.

• The address is placed on the address bus (FIG. 1).

• The address is decoded by the logic (partly on the memory chip), to select the location for reading.

• Reading is enabled. In FIG. 1 the WRITE ENABLE line is already high so reading is already enabled.

• The CHIP ENABLE line goes low, to switch the memory chip outputs from the high-impedance state to a low-impedance state (outputs high or low).

• Data from the memory location appears on the data bus.

• The CPU stores the data from the bus in its data bus register.

• The data is transferred to the internal bus of the CPU.

• If the data is an instruction, it is stored in the instruction register.

• While the last two states are occurring, the CE line goes high and the data is no longer present on the data bus.

Assuming that the data is an instruction, the logic of the control unit causes one of many different actions to occur.

Example:

The data register of an 8085 CPU holds the following byte:

01001111


FIG. 1 Reading a byte from memory involves a sequence of logic levels on two lines of the control bus. The WRITE ENABLE line stays high as this is a read operation. Data from memory is put on the bus when the CHIP ENABLE line goes low.

The latches of the register are either set (= 1) or reset (= 0).

When the control unit receives this data it copies to register C (a general-purpose register) the data that is in register A (the accumulator). This is an internal movement of data so a single byte is enough to tell the control unit what to do. How the control logic works, that is to say, how the input of 01001111 makes the control logic take the action we have described, is outside the scope of this guide.

Example:

At another time, the data register holds:

00110111

This instructs the control unit to set the carry bit of the status register to '1'. As above, a single byte is all that is necessary for this instruction. It is obvious that, if we always had to write out data as 8-bit binary numbers, as above, it would be all too easy to make mistakes. Instead, we express the numbers in hexadecimal. So the two instructions described above become:

4F, for copying data from A to C, and 37, for setting the carry bit

Some operations require two bytes. The first byte states what is to be done. On loading this byte, the CPU loads the next byte in memory to find out what value to operate on.

Example: The code E6 (actually 11100110) tells the control unit to AND register A with the binary value that is in the next byte. The control unit has to send the CPU back to memory to find out the value of this byte, which is stored in the next location to the instruction E6. Therefore, the full instruction might be:

E6 4A

This tells the control unit to AND the content of register A with the value 4A.

Writing to memory

A sequence of operations may often end in the storing of a result in memory. There are opcodes for storing the contents of various registers. The operand is the address at which the data is to be stored.

In a 16-bit system, it takes two bytes to specify the address. This means that the opcode is followed by two bytes, making the instruction three bytes long. The sequence of storing data is almost the reverse of the sequence for reading data. It has nine stages:

• The address to which the byte is to be written is transferred along the internal bus of the CPU to the address bus register.

• The address is placed on the address bus (Fig. 2).

• The address is decoded by the logic (partly on the memory chip), to select the location for writing to.

• The data from the register (often the accumulator) is placed on the internal bus.

• The data is latched into the data bus buffer.

• The data appears on the data bus (Fig. 2).

• Writing is enabled. In Fig. 2, the WRITE ENABLE line is made low.

• The CHIP ENABLE line goes low, to store the data from the bus in the addressed location.

• The CE line goes high, followed by the WE line. The address and data are no longer present on their busses.

=====================

Bitwise logic

CPU logic is always done bitwise, that is, corresponding bits in two values are subjected to the logical operation.

Example: Suppose that, as the result of a previous operation, register A holds the value $AC. To AND this bitwise with $4A we must first write out the values in binary:

10101100

01001010

The rules for the AND operation are:

0 • 0 = 0

0 • 1 = 0

1 • 0 = 0

1 • 1 = 1

Note that in these equations '•' represents the operator

'AND'. The result on the right is true (= 1) only if both bits on the left are 1.

In the example, only the fourth pair of bits from the right is 1•1. The result of ANDing is:

00001000

In hex, this is $08, which is the value that would be left in register A at the end of the operation.

Opcodes and operands

The first byte of an instruction tells the CPU what type of operation to perform. It is known as the operational code, or opcode.

Sometimes the opcode is followed by one or two more bytes, which give the CPU something on which to operate.

It may be a value to work on, or it may be an address where a value will be found stored. This byte or pair of bytes is known as the operand.

=====================

The instructions for a program are stored in memory (RAM or ROM) as a sequence of consecutive bytes, which are the opcodes, and operands of a program. This is known as machine code. It is the only form of program on which the machine (the CPU) can work. If the program is first written in machine code, this is also known as the source code.

Assembler

Writing in machine code is not easy. Fortunately, there is software that makes it possible to write the source code in other forms and have it automatically turned into machine code. The simplest type of software is assembler. It is much easier to write in assembler than in machine code. One reason is that we write assembler in mnemonics instead of in opcodes. A mnemonic is a group of up to four letters to represent each opcode and there is a different mnemonic for each opcode. The point of mnemonics is that the letters help us remember what the operation does.

Example: The instruction 00110111 on the data bus is the equivalent of $37. The opcode $37 tells the CPU to set the carry bit in the status register. If we are programming the system in assembler, the corresponding mnemonic is STC. This is easier to remember because the three letters remind us of the action:

'SeT Carry'. So we type in STC and the assembler program converts this to the opcode '37' and adds it to the program.

Different assemblers may use slightly different mnemonics but the principle is the same for all. Some of the mnemonics may be identical in different assemblers. An assembler is written with a particular CPU in mind. It has, for example, to take into account the widths and numbers of registers that the CPU has, and whether or not they can be used for arithmetical and logic operations. Some CPUs can do things that others can not do. In such cases, the assembler may include special mnemonics for managing these particular actions.

Although an assembler simplifies program writing by using mnemonics, it still requires us to give the CPU its instructions systematically.

Example:

Here is a short section of program written in the assembler of the '1200' microcontroller:

ldi r17,37 inc r17 mov r5,r17 In the '1200' assembler, numbers prefixed with 'r' refer to one of the CPUs 32 registers. Values are taken to be decimal unless prefixed by the $ symbol. Given this information, it is easy to see that this sequence means:

Load register 17 with the value 37.

Increment register 17.

Move (or copy) to register 5 the content of register 17.


FIG. 2 The timing of signals on the busses and control lines must allow logic levels to settle and for logical responses to be completed.

The sequences illustrated here and in FIG. 1 normally take at least 3 or 4 cycles of the system clock, possibly up to 12. If the system clock is running at 100 MHz, four cycles take 40 ns.

========

Learning assembler

The only way to learn how to use an assembler is to practice writing programs. There are many suggestions for programming exercises in this guide. Many different microcontrollers are suitable for this work and each has its own assembler program. The example in this section is based on the assembler for one of them, the Atmel '1200' microcontroller.

The assembler you use may differ in detail from the examples given here. However, the examples used in this SECTION are short and they are simple in structure, so it is easy for you to find the equivalents on your assembler.

========

With the assembler program running in a PC or similar computer, the program listing given above is typed in. It appears on the computer screen just as shown above. In this guide, we use a different style of type to indicate programs and other screen displays.

This style is the same as used by our assembler and many others to display text on the screen.

Another advantage of assembler is that we can use decimal numbers instead of having to convert every value into hex. The assembler does all the converting when it turns our source code program into machine code.

When the program is typed in, it appears in a window on the screen. If it appears to be correct, clicking on the 'Assemble' button at the top of the screen causes the program to assemble it, that is, to turn it into machine code. This is much quicker than looking up a table of opcodes. The machine code is stored in a file, with the extension .obj.

At this stage, instead of assembling, the assembler may report that there are errors in the program. It states the number of line or lines on which the errors occur and gives a brief description of the type of error.

Example:

The first version of this program used register 2 where it now uses register 17. On assembling, an error was reported in line (1). This was because only registers 16 to 31 can be used for arithmetical operations, including loading numerical values.

After the program had been edited to change 'r2' to 'r17', assembly proceeded without error.

When a program is being typed in, it appears in a special window on the screen. It is possible to have other windows open on the screen at the same time. One useful window is the Processor window. This displays the state of the program counter, the stack pointer, the flags and other useful data. Another window, of special use in checking the short example program above, is the Register window. This shows the content of all 32 registers. It makes it simple to follow the changes in the values held in registers as the program is run. If you simply 'run' the program by clicking on the 'run' icon in the toolbar, the program runs so fast that it is impossible to keep track of all the changes.

Instead, click on the 'single step' icon. Then the program runs one line each time you click, and you can look at the results of each step. You can see the changes in the values stored in the registers and, at the same time, watch for changes in the program counter and in the flags in the status register. This is another big advantage of using an assembler program.

Example:

The easiest way to follow the changes is to run through the program one line at a time. At the start of the program, all registers hold zero. After the first line, r17 holds '25'. (although we are working in decimal, the assembler is working in hex).

After the second line, r17 changes to '26'. After the third line, r17 still holds '26' and the same value is copied to r5.

At the same time, the program counter in the Processor window begins at 0, then changes to 1, then to 2, and then to 3. There are no changes in the flags.

As a reminder that the essential thing about an assembler is that it assembles machine code for us, it is possible to display the actual machine code of the short program. The memory of the '1200' is 16 bits wide, so it can store both the opcode and the operand in a single 16-bit word. The numbered memory locations are listed on the left, and in the next column, we see the machine code. The original assembler listing is repeated on the right.

===========

Errors

There are three main kinds of programming error:

• Telling the CPU to do something it can not do.

• Telling the CPU to do something it can not understand.

• Telling the CPU to do something it can understand and do, but which produces a result you did not intend.

The assembler (and most other software for producing programs) will report the first two kinds of error. An example of the first kind is mentioned in the text. An example of the second kind is if you make a typing error, typing ICN instead of INC. As another example, you may follow the opcode with too few or too many operands.

These errors are often reported as syntax errors.

Errors of the third kind are the programmer's responsibility and are not reported. Provided that the CPU can do what you tell it, it will do it. This is why you need to debug your programs thoroughly. The assembler program may include a section for debugging, or you may use a separate debugging program.

===========

Example:

The display of memory content of the example program looks like this:

+00000000: E215 LDI R17,0x25

+00000001: 9513 INC R17

+00000002: 2E51 MOV R5,R17

This display reveals that the program occupies the first three 16-bit memory locations, which contain E215, 9513, 2E51, in that order. We shall not be looking at the machine code after this.

On the screen, there is a further entry on the extreme right of the first line:

; 0x25 = 0b00100101 = 37

This is a comment or remark put there by the assembler. It explains that the hex value 25, corresponds to the binary value 00100101 and to the decimal value 37. When the assembler lists a program or makes comments, a hex value begins with 0x.

A binary value begins with 0b and a decimal value is written as we would normally write it. Other assemblers may set out values differently.

Note the semicolon preceding the comment. This is a conventional way of indicating that what follows on that line is not part of the program. We shall return to this point in a moment.

Whether or not the assembler that you are using has exactly the same features as the one described here, the point is that an assembler allows you to type in a program and assemble it. It searches for and reports errors. It allows you to view the program in both assembler and in machine code. What it does not do is inform you if what you intended the CPU to do, is not the same as what you have actually told it to do.

This is where debugging is important.

Improving the program

The bare program as listed earlier is all that is essential, but can be made more understandable to the programmer and to others who may want to study it and perhaps modify or extend it. This is done by adding a title and some comments or remarks. These are preceded by a semi-colon. When assembling, anything between the semi-colon and the end of the line is ignored by the assembler. However, it may be of great interest to programmers and others.

Example This is one way in which the sample program can be improved by a title and comments:

; Program example 1, introducing assembler ldi r17, 37 ; loads register 17 with

; blood temperature.

inc r17 ; increments temperature.

mov r5,r17 ; stores incremented temp in

; base register.

Note the positions of the semi-colons. The title explains what the program is about. The comments on the right explain what happens at each step. Long comments can be carried on to the next line (after a semi-colon).

Comments are important because an uncommented program of more than a few lines is extremely difficult to understand, even by the person who wrote it, and especially a few weeks after it is written. Writing useful comments is an acquired skill. They must not be so brief as to be unintelligible. They must not be so long that the actual program becomes 'lost' in a mass of multi-line comments.

Another advantage of assembler compared with machine code is that assembler allows us to use labels. A label is a word used to identify a:

• program line.

• register.

• variable (number).

Using labels makes it much easier to understand what a program is doing and how the different parts of it link together. Labels must be defined at the beginning of the program.

Example:

; Program example 1, introducing assembler

.def counter = r17

.def trial = r5

.equ body = 37 ldi counter, body ; loads r17 with

; body temp.

inc counter ; increment temp.

mov trial, counter ; store in trial.

After the title, but before the actual program listing, are two definitions (.def) which tell the assembler the names we are giving to two of the CPUs registers. In the program, we always refer to the register by its name instead of its number. There is also a statement to say that the label body is equal (.equ) to 37. This is actually an extra step that was not included in the previous program. There we directly loaded counter with 37. Now we are loading 37 into a constant called body. After that, we copy the value of body into counter. The name body can be used anywhere later in the program where we need to use the value of body temperature. In addition, if for any reason we want to change the value throughout the program, we can simply change it in the .equ definition.

Rarely does a program run straight through from beginning to end.

More often, the CPU is required to jump from one part of the program to another, often skipping back to repeat a part of the program several times. In machine code, there are instructions to tell the CPU to jump, and giving it the address in memory to jump to. This is another cause of difficulty. Calculating the exact number of bytes to jump over, and expressing that number in hex is a frequent source of error. If the program is subsequently altered, perhaps by inserting extra sections of code, many of the 'jump to' addresses will be wrong. It is then necessary to work through the program correcting all the addresses.

There is the further point that an address itself (example $E28A) has little meaning to the programmer or to someone else reading the program. Fortunately, an assembler allows us to label the lines of the program, so that the CPU can be told to jump to this named line. Even if we insert new pieces of program, the label stays with its line and there is no need to correct it.

Example:

The sample program processes the value stored in trial, then has to come back to start work on the next (incremented) value.

It needs to jump back to the line in which the value in count is incremented. We place a label again at the beginning of this line to indicate the line to which the CPU is to jump back.

;Program example 1, introducing assembler

.def counter = r17

.def trial = r5

.equ body = 37

ldi counter,body ; loads r17 with

; body temp.

again: inc counter ; incrementtemp.

mov trial,counter; store in trial.

===========

A dry run

In the early stages of writing a program or a section of a program it often helps to take paper and pencil and work out how the values in the registers and in memory will change, line-by-line.

This is known as a dry run. The same kind of tryout can be run using the assembler program or some of the high-level languages, but things may seem clearer to you on paper than on the screen. In addition, it is possible to cross out and scribble comments on paper, but less easy to do this on the screen.

Here is a dry run of the assembler program:


The columns show the contents of the registers after each line has been run.

===========

Later in the program, when the CPU has finished working on the current value of trial, it is told to jump back to again.

Then trial is incremented and the CPU does the processing again, using the new value.

This completes our account of the principles of assembler programs.

Sumamry, its main advantages are:

• Easily remembered mnemonics.

• Can use decimal numbers.

• Comments.

• Automatic assembly.

• Error reporting.

• Labels for addresses, registers, and variables.

There is more to be learned about assembler, but this is best done by working the practical examples in the EXERCISE sections below.

==========

EXERCISE 1 -- Starting assembler

You need:

• A computer with assembler software installed.

• The manual for the assembler (assuming that you already know how to use the computer).

Find out how to enter programs into the assembler. Try out any working examples for beginners that are explained in the manual. You are not expected to memorize or to use all of the instruction mnemonics. As you work these activities, write on a piece of card the mnemonics and directives that you actually use. This will give you a quick reference list of the most useful (to you) of the mnemonics and directives.

Look for the mnemonics and directives that most closely match the ones we have used in the example program above. Use these to write a version of our program that will make your chosen microprocessor or controller perform the same task. The set of registers you have available may be similar to those of our '1200' microcontroller, but may be different. If they are different, remember that the aim of the program is to get the CPU to load a number, to increment it, and to store it in another register. If you do not have enough registers for this, store it at an address in RAM.

Assemble the program you have written and correct any errors that you have made.

Run your program, single-stepping it, and watching the changes that occur in the register, the program counter, and the status register.

EXERCISE 2 -- Adapting the program

You need:

• A computer with assembler software installed.

• A user manual for the assembler.

• Your card of frequently used mnemonics.

If you have not already done so, improve the program you wrote in EXERCISE 1 by adding a title and comments.

Re-write the program to make the CPU do something slightly different. For example, make it load a different number. Make it store the incremented number in a different register. Make it increment the number twice before storing it. Make it decrement the number instead of incrementing it. Using the mnemonics from your version of our program and perhaps one or two others from your instruction set, there are many different ways of programming the CPU to do something slightly different.

Add comments to each short program you write, so that others will be able to understand what it is supposed to do.

==========

Problems on: Instructions

1. What is a fetch-execute cycle? Give examples based on a CPU that you are studying.

2. Describe a read cycle, illustrating it by a timing diagram (not necessarily to scale) for the CPU you are studying.

3. Describe a write cycle, illustrating it by a timing diagram (not necessarily to scale) for the CPU you are studying.

4. What is machine code? Give examples of a few opcodes and explain the operands that they require.

5. What is an assembler program and what are its advantages over writing a program in machine code?

6. List a short assembler program that you have written. Explain concisely what the CPU does at each stage.

7. Do a dry run of the program you described in your answer to Question 6.

Answers to questions

PREV. | NEXT

Related Articles -- Top of Page -- Home

Updated: Thursday, May 18, 2017 10:10 PST