ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 14.06.2025

Просмотров: 1069

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

12

CHAPTER 2. MICROCONTROLLER COMPONENTS

Arithmetic Logic Unit

At the core of the CPU is the arithmetic logic unit (ALU), which is used to perform computations (AND, ADD, INC, . . . ). Several control lines select which operation the ALU should perform on the input data. The ALU takes two inputs and returns the result of the operation as its output. Source and destination are taken from registers or from memory. In addition, the ALU stores some information about the nature of the result in the status register (also called condition code register):

Z (Zero): The result of the operation is zero.

N (Negative): The result of the operation is negative, that is, the most significant bit (msb) of the result is set (1).

O (Overflow): The operation produced an overflow, that is, there was a change of sign in a two’s- complement operation.

C (Carry): The operation produced a carry.

Two’s complement

Since computers only use 0 and 1 to represent numbers, the question arose how to represent negative integer numbers. The basic idea here is to invert all bits of a positive integer to get the corresponding negative integer (this would be the one’s complement). But this method has the slight drawback that zero is represented twice (all bits 0 and all bits 1). Therefore, a better way is to represent negative numbers by inverting the positive number and adding 1. For +1 and a 4-bit representation, this leads to:

1 = 0001 → −1 = 1110 + 1 = 1111.

For zero, we obtain

0 = 0000 → −0 = 1111 + 1 = 0000,

so there is only one representation for zero now. This method of representation is called the two’s complement and is used in microcontrollers.

Register File

The register file contains the working registers of the CPU. It may either consist of a set of general purpose registers (generally 16–32, but there can also be more), each of which can be the source or destination of an operation, or it consists of some dedicated registers. Dedicated registers are e.g. an accumulator, which is used for arithmetic/logic operations, or an index register, which is used for some addressing modes.

In any case, the CPU can take the operands for the ALU from the file, and it can store the operation’s result back to the register file. Alternatively, operands/result can come from/be stored to the memory. However, memory access is much slower than access to the register file, so it is usually wise to use the register file if possible.

2.1. PROCESSOR CORE

13

Example: Use of Status Register

The status register is very useful for a number of things, e.g., for adding or subtracting numbers that exceed the CPU word length. The CPU offers operations which make use of the carry flag, like ADDCa (add with carry). Consider for example the operation 0x01f0 + 0x0220 on an 8-bit CPUb c:

CLC

; clear carry flag

LD R0,

#0xf0

; load first low byte into register R0

ADDC R0,

#0x20 ; add 2nd low byte with carry (carry <- 1)

LD R1,

#0x01 ; load first high byte into R0

ADDC R1, #0x02

; add 2nd high byte, carry from

; previous ADC is added

The first ADDC stores 0x10 into R0, but sets the carry bit to indicate that there was an overflow. The second ADDC simply adds the carry to the result. Since there is no overflow in this second operation, the carry is cleared. R1 and R0 contain the 16 bit result 0x0410. The same code, but with a normal ADD (which does not use the carry flag), would have resulted in 0x0310.

aWe will sometimes use assembler code to illustrate points. We do not use any specific assembly language or instruction set here, but strive for easily understood pseudo-code.

bA # before a number denotes a constant.

cWe will denote hexadecimal values with a leading $ (as is generally done in Assembly language) or a leading 0x (as is done in C).

Stack Pointer

The stack is a portion of consecutive memory in the data space which is used by the CPU to store return addresses and possibly register contents during subroutine and interrupt service routine calls. It is accessed with the commands PUSH (put something on the stack) and POP (remove something from the stack). To store the current fill level of the stack, the CPU contains a special register called the stack pointer (SP), which points to the top of the stack. Stacks typically grow “down”, that is, from the higher memory addresses to the lower addresses. So the SP generally starts at the end of the data memory and is decremented with every push and incremented with every pop. The reason for placing the stack pointer at the end of the data memory is that your variables are generally at the start of the data memory, so by putting the stack at the end of the memory it takes longest for the two to collide.

Unfortunately, there are two ways to interpret the memory location to which the SP points: It can either be seen as the first free address, so a PUSH should store data there and then decrement the stack pointer as depicted in Figure 2.21 (the Atmel AVR controllers use the SP that way), or it can be seen as the last used address, so a PUSH first decrements the SP and then stores the data at the new address (this interpretation is adopted for example in Motorola’s HCS12). Since the SP must be initialized by the programmer, you must look up how your controller handles the stack and either initialize the SP

1Do not be confused by the fact that the SP appears to increase with the PUSH operation. Memory is generally depicted with the smallest address at the top and the largest address ($FF in our case) at the bottom. So if the SP goes up, its value decreases.


14

CHAPTER 2. MICROCONTROLLER COMPONENTS

Push 0x01

0x01

SP

0x01

SP

$FF

$FF

Push 0x02

0x02

SP

0x02

SP

0x01

0x01

$FF

$FF

Pop R2

SP 0x02

SP

0x02

R0

0x02

0x01

0x01

$FF

$FF

Figure 2.2: Stack operation (decrement first).

to the last address in memory (if a push stores first and decrements afterwards) or to the last address + 1 (if the push decrements first).

As we have mentioned, the controller uses the stack during subroutine calls and interrupts, that is, whenever the normal program flow is interrupted and should resume later on. Since the return address is a pre-requisite for resuming program execution after the point of interruption, every controller pushes at least the return address onto the stack. Some controllers even save register contents on the stack to ensure that they do not get overwritten by the interrupting code. This is mainly done by controllers which only have a small set of dedicated registers.

Control Unit

Apart from some special situations like a HALT instruction or the reset, the CPU constantly executes program instructions. It is the task of the control unit to determine which operation should be executed next and to configure the data path accordingly. To do so, another special register, the program counter (PC), is used to store the address of the next program instruction. The control unit loads this instruction into the instruction register (IR), decodes the instruction, and sets up the data path to execute it. Data path configuration includes providing the appropriate inputs for the ALU (from registers or memory), selecting the right ALU operation, and making sure that the result is written to the correct destination (register or memory). The PC is either incremented to point to the next instruction in the sequence, or is loaded with a new address in the case of a jump or subroutine call. After a reset, the PC is typically initialized to $0000.

Traditionally, the control unit was hard-wired, that is, it basically contained a look-up table which held the values of the control lines necessary to perform the instruction, plus a rather complex decoding logic. This meant that it was difficult to change or extend the instruction set of the CPU. To ease the design of the control unit, Maurice Wilkes reflected that the control unit is actually a small CPU by itself and could benefit from its own set of microinstructions. In his subsequent control unit design, program instructions were broken down into microinstructions, each of which did some small part of the whole instruction (like providing the correct register for the ALU). This essentially made control design a programming task: Adding a new instruction to the instruction set boiled down to programming the instruction in microcode. As a consequence, it suddenly became comparatively


2.1. PROCESSOR CORE

15

easy to add new and complex instructions, and instruction sets grew rather large and powerful as a result. This earned the architecture the name Complex Instruction Set Computer (CISC). Of course, the powerful instruction set has its price, and this price is speed: Microcoded instructions execute slower than hard-wired ones. Furthermore, studies revealed that only 20% of the instructions of a CISC machine are responsible for 80% of the code (80/20 rule). This and the fact that these complex instructions can be implemented by a combination of simple ones gave rise to a movement back towards simple hard-wired architectures, which were correspondingly called Reduced Instruction Set Computer (RISC).

RISC: The RISC architecture has simple, hard-wired instructions which often take only one or a few clock cycles to execute. RISC machines feature a small and fixed code size with comparatively few instructions and few addressing modes. As a result, execution of instructions is very fast, but the instruction set is rather simple.

CISC: The CISC architecture is characterized by its complex microcoded instructions which take many clock cycles to execute. The architecture often has a large and variable code size and offers many powerful instructions and addressing modes. In comparison to RISC, CISC takes longer to execute its instructions, but the instruction set is more powerful.

Of course, when you have two architectures, the question arises which one is better. In the case of RISC vs. CISC, the answer depends on what you need. If your solution frequently employs a powerful instruction or addressing mode of a given CISC architecture, you probably will be better off using CISC. If you mainly need simple instructions and addressing modes, you are most likely better off using RISC. Of course, this choice also depends on other factors like the clocking frequencies of the processors in question. In any case, you must know what you require from the architecture to make the right choice.

Von Neumann versus Harvard Architecture

In Figure 2.1, instruction memory and data memory are depicted as two separate entities. This is not always the case, both instructions and data may well be in one shared memory. In fact, whether program and data memory are integrated or separate is the distinction between two basic types of architecture:

Von Neumann Architecture: In this architecture, program and data are stored together and are accessed through the same bus. Unfortunately, this implies that program and data accesses may conflict (resulting in the famous von Neumann bottleneck), leading to unwelcome delays.

Harvard Architecture: This architecture demands that program and data are in separate memories which are accessed via separate buses. In consequence, code accesses do not conflict with data accesses which improves system performance. As a slight drawback, this architecture requires more hardware, since it needs two busses and either two memory chips or a dual-ported memory (a memory chip which allows two independent accesses at the same time).

2.1.2Instruction Set

The instruction set is an important characteristic of any CPU. It influences the code size, that is, how much memory space your program takes. Hence, you should choose the controller whose instruction set best fits your specific needs. The metrics of the instruction set that are important for a design decision are

16

CHAPTER 2. MICROCONTROLLER COMPONENTS

Example: CISC vs. RISC

Let us compare a complex CISC addressing mode with its implementation in a RISC architecture. The 68030 CPU from Motorola offers the addressing mode “memory indirect preindexed, scaled”:

MOVE D1, ([24,A0,4*D0])

This operation stores the contents of register D1 into the memory address

24 + [A0] + 4 [D0]

where square brackets designate “contents of” the register or memory address.

To simulate this addressing mode on an Atmel-like RISC CPU, we need something like the following:

LD

R1, X

; load data indirect (from [X] into R1)

LSL

R1

; shift left -> multiply with 2

LSL

R1

; 4*[D0] completed

MOV

X, R0

; set pointer (load A0)

LD

R0, X

; load indirect ([A0] completed)

ADD

R0, R1

; add obtained pointers ([A0]+4*[D0])

LDI

R1, $24

; load constant ($ = hex)

ADD

R0, R1

; and add (24+[A0]+4*[D0])

MOV

X, R0

; set up pointer for store operation

ST

X, R2

; write value ([24+[A0]+4*[D0]] <- R2)

In this code, we assume that R0 takes the place of A0, X replaces D0, and R2 contains the value of D1.

Although the RISC architecture requires 10 instructions to do what the 68030 does in one, it is actually not slower: The 68030 instruction takes 14 cycles to complete, the corresponding RISC code requires 13 cycles, assuming that all instructions take one clock cycle, except memory load/store, which take two.

Instruction Size

Execution Speed

Available Instructions

Addressing Modes

Instruction Size

An instruction contains in its opcode information about both the operation that should be executed and its operands. Obviously, a machine with many different instructions and addressing modes requires longer opcodes than a machine with only a few instructions and addressing modes, so CISC machines tend to have longer opcodes than RISC machines.

Note that longer opcodes do not necessarily imply that your program will take up more space than on a machine with short opcodes. As we pointed out in our CISC vs. RISC example, it depends on


2.1. PROCESSOR CORE

17

Example: Some opcodes of the ATmega16

The ATmega16 is an 8-bit harvard RISC controller with a fixed opcode size of 16 or in some cases 32 bits. The controller has 32 general purpose registers. Here are some of its instructions with their corresponding opcodes.

instruction

result

operand conditions

opcode

ADD Rd, Rr

Rd + Rd ← Rr

0

≤ d ≤ 31,

0000

11rd dddd rrrr

Rd ← Rd & Rr

0

≤ r ≤ 31

AND Rd, Rr

0

≤ d ≤ 31,

0010

00rd dddd rrrr

NOP

0

≤ r ≤ 31

0000

0000 0000 0000

LDI Rd, K

Rd ← K

16 ≤ d ≤ 31,

1110 KKKK dddd KKKK

Rd ← [k]

0

≤ K ≤ 255

LDS Rd, k

0

≤ d ≤ 31,

1001

000d dddd 0000

0

≤ k ≤ 65535

kkkk kkkk kkkk kkkk

Note that the LDI instruction, which loads a register with a constant, only operates on the upper 16 out of the whole 32 registers. This is necessary because there is no room in the 16 bit to store the 5th bit required to address the lower 16 registers as well, and extending the operation to 32 bits just to accommodate one more bit would be an exorbitant waste of resources.

The last instruction, LDS, which loads data from the data memory, actually requires 32 bits to accommodate the memory address, so the controller has to perform two program memory accesses to load the whole instruction.

what you need. For instance, the 10 lines of ATmega16 RISC code require 20 byte of code (each instruction is encoded in 16 bits), whereas the 68030 instruction fits into 4 bytes. So here, the 68030 clearly wins. If, however, you only need instructions already provided by an architecture with short opcodes, it will most likely beat a machine with longer opcodes. We say “most likely” here, because CISC machines with long opcodes tend to make up for this deficit with variable size instructions. The idea here is that although a complex operation with many operands may require 32 bits to encode, a simple NOP (no operation) without any arguments could fit into 8 bits. As long as the first byte of an instructions makes it clear whether further bytes should be decoded or not, there is no reason not to allow simple instructions to take up only one byte. Of course, this technique makes instruction fetching and decoding more complicated, but it still beats the overhead of a large fixed-size opcode. RISC machines, on the other hand, tend to feature short but fixed-size opcodes to simplify instruction decoding.

Obviously, a lot of space in the opcode is taken up by the operands. So one way of reducing the instruction size is to cut back on the number of operands that are explicitly encoded in the opcode. In consequence, we can distinguish four different architectures, depending on how many explicit operands a binary operation like ADD requires:

Stack Architecture: This architecture, also called 0-address format architecture, does not have any explicit operands. Instead, the operands are organized as a stack: An instruction like ADD takes the top-most two values from the stack, adds them, and puts the result on the stack.

Accumulator Architecture: This architecture, also called 1-address format architecture, has an ac-