Micro Processor and Micro Controller

Chapter 3
Instruction-Set Architecture and Assembly Programming

Post Graduate Program in Electrical Engineering
Micro Processor and Micro Controller   •   Post Graduate

3. Instruction-Set Architecture and Assembly Programming

Introduction and Context

A microprocessor or microcontroller is programmed through an instruction set: the collection of machine operations that the processor can recognize and execute. Although high-level languages improve productivity, assembly language remains important in embedded systems, device drivers, real-time control, boot software, digital signal processing, and performance-critical routines. Assembly programming exposes the relationship between software instructions, registers, memory, status flags, buses, and the arithmetic-logic unit.

An instruction-set architecture (ISA) is the programmer-visible specification of a processor. It defines registers, data types, addressing modes, instruction formats, memory organization, exception behavior, and the effects of instructions on processor state. The ISA must be distinguished from microarchitecture. Two processors may implement the same ISA using different internal datapaths, pipelines, caches, or control logic. Thus, an assembly program describes the required architectural operations, while the implementation determines how those operations are physically performed.

This chapter uses representative 8-bit, 16-bit, and 32-bit processors to develop transferable principles. The 8085 illustrates a compact accumulator-oriented 8-bit architecture, the 8086 demonstrates a 16-bit segmented architecture, and ARM provides an example of a modern 32-bit load/store approach. The exact mnemonics and register conventions differ, but the underlying programming tasks—moving data, performing arithmetic, testing conditions, controlling program flow, using the stack, and organizing subroutines—remain remarkably consistent.

Learning Objectives

After studying this chapter, the learner should be able to:

1. Explain the role and components of an instruction-set architecture.

2. Distinguish instruction formats, operands, addressing modes, and processor flags.

3. Apply data-transfer, arithmetic, logical, branching, stack, and subroutine instructions.

4. Compare representative features of 8085, 8086, and 32-bit ARM instruction sets.

5. Develop assembly programs for arithmetic, comparison, looping, table processing, and bit manipulation.

6. Trace register, memory, stack, and flag changes during program execution.

7. Identify common assembly-programming errors and apply systematic debugging methods.

8. Optimize assembly programs while preserving correctness, timing requirements, and maintainability.

The Programmer’s View of a Processor

At any instant, processor execution can be described by its architectural state. This state normally includes a program counter, instruction register, general-purpose registers, status flags, stack pointer, and sometimes special registers such as an index register or link register. The program counter identifies the address of the next instruction. During instruction execution, the processor fetches an instruction from memory, decodes its operation and operands, performs the operation, updates relevant flags, and advances or changes the program counter.

An instruction consists conceptually of an operation code and zero or more operands. For example, an addition instruction may specify two registers, a register and an immediate constant, or a register and a memory location. The operand specification is determined by the instruction format and addressing mode. The programmer must understand not only what result an instruction produces but also which flags it modifies, whether it accesses memory, and whether it changes control flow.

A status register contains condition codes generated by arithmetic and logical operations. Common flags include:

  • Carry: indicates a carry out of the most significant bit or a borrow-related condition, depending on the architecture and operation.
  • Zero: set when the result is zero.
  • Sign or negative: reflects the most significant bit in signed arithmetic systems.
  • Parity: indicates whether the result contains an even or odd number of one bits.
  • Auxiliary or half carry: records carry between lower and upper portions of a byte or word in architectures that support it.
  • Overflow: indicates that a signed result cannot be represented in the available number of bits.

Carry and overflow must not be confused. For an unsigned addition, carry indicates that the result exceeds the unsigned range. For signed arithmetic, overflow indicates that the sign interpretation is invalid. For example, adding two positive 8-bit signed values may produce a negative bit pattern; this is signed overflow even if the unsigned carry flag is clear.

Addressing Modes and Instruction Representation

Addressing modes specify how an instruction obtains its operands. Immediate addressing places the operand value in the instruction itself, as in loading a register with a constant. Register addressing uses the contents of a named register. Direct or absolute addressing includes a memory address in the instruction. Register-indirect addressing uses a register containing the effective memory address. Indexed and based addressing calculate an address by adding a displacement to one or more registers. Relative addressing forms a branch target by adding a signed displacement to the current program counter.

The choice of addressing mode affects program size, execution time, flexibility, and portability. Immediate operands are efficient for constants but cannot represent data that changes in memory. Register operations are usually fast, whereas memory access may require additional bus cycles. Indexed addressing is particularly useful for arrays and tables. Relative branches reduce code size and support relocatable code, but their range may be limited.

Instruction sets are often described as RISC or CISC, although modern processors incorporate features from both traditions. RISC designs generally use regular instruction formats, many registers, and load/store memory access. CISC designs may provide variable-length instructions and memory operands in arithmetic operations. The 8085 and 8086 illustrate historically compact and feature-rich instruction styles, while ARM commonly illustrates a regular load/store organization.

Families of Instructions

Data Transfer

Data-transfer instructions copy information without intentionally changing its value. Typical operations include load, store, move, exchange, push, and pop. In an 8085-style program, `MVI A, 25H` loads the accumulator with the hexadecimal value 25H, while `MOV B, A` copies the accumulator into register B. `LDA 2050H` loads the accumulator from memory and `STA 2051H` stores it to memory.

In the 8086, `MOV AX, 1234H` loads a 16-bit immediate value, and `MOV AL, [SI]` transfers a byte from the memory address held in SI. In a 32-bit ARM-like syntax, `MOV R0, #25` transfers an immediate value to a register, while `LDR R1, [R2]` loads from memory and `STR R1, [R2]` stores to memory. A load/store architecture normally does not permit an arithmetic instruction to operate directly on memory; the data must first be loaded into a register.

Care is necessary when moving data of different widths. A byte placed in a larger register may require zero extension or sign extension. Zero extension fills upper bits with zeros; sign extension copies the sign bit. Confusing these operations produces errors when signed values are processed.

Arithmetic Instructions

Arithmetic instructions include addition, subtraction, increment, decrement, multiplication, division, and compare. A compare instruction performs a subtraction for flag-setting purposes but normally discards the numerical result. It is therefore central to conditional programming.

An 8-bit addition can be represented as

`A + B = R`

where R is an 8-bit result and the carry flag records a ninth bit if one is generated. Multi-byte arithmetic extends this process. The lower byte is added first, followed by the higher byte using an add-with-carry instruction. Similarly, multi-byte subtraction uses subtract-with-borrow. The programmer must preserve or deliberately use the carry condition between successive operations.

For example, an 8085-style 16-bit addition may be written:

```asm

LHLD 2050H ; HL <- first 16-bit operand

XCHG ; DE <- first operand

LHLD 2052H ; HL <- second operand

DAD D ; HL <- HL + DE

SHLD 2054H ; store result

```

The example demonstrates register-pair arithmetic, but it also illustrates a limitation: the programmer must know which register pair an instruction implicitly uses. Such implicit operands are common in older accumulator-oriented processors.

Logical and Bit-Manipulation Instructions

Logical instructions include AND, OR, exclusive OR, complement, and test operations. They are used for masking, setting, clearing, toggling, and testing individual bits. To clear selected bits, an AND mask is used. To set selected bits, an OR mask is used. To toggle selected bits, an exclusive-OR mask is used.

Suppose bit 3 of an input byte represents an alarm. The expression `(value AND 08H)` isolates that bit. If the result is zero, the alarm is inactive; otherwise it is active. A mask must be selected carefully because unrelated bits may control other hardware functions.

Shift and rotate instructions support multiplication or division by powers of two, serial communication, bit-field extraction, and cyclic redundancy calculations. A logical shift inserts zeros, whereas an arithmetic right shift preserves the sign bit in signed representations. Rotate-through-carry operations treat the carry flag as an additional bit and are useful in multi-byte shifts.

Branching and Control Flow

Sequential execution is modified by jump, branch, call, return, and interrupt instructions. An unconditional jump always transfers control. A conditional branch examines one or more flags. Typical conditions include equal or not equal, carry or no carry, higher or lower for unsigned comparisons, and greater or less for signed comparisons.

A loop generally contains initialization, a body, a counter update, and a termination test. A robust loop must specify whether the count represents the number of executions or the final counter value. Off-by-one errors often arise when the counter is tested before rather than after the body.

A comparison must be interpreted according to the data type. After subtracting two unsigned values, carry and zero commonly support lower, higher, and equal tests. Signed comparisons require a combination of sign and overflow information on architectures that provide both. Using an unsigned branch for signed data is a logical error even though the program may appear to work for positive test values.

Stack and Subroutine Instructions

The stack is a last-in, first-out memory structure addressed by the stack pointer. It stores return addresses, saved registers, local data, interrupt context, and temporary values. A push operation places data on the stack and updates the stack pointer; a pop operation retrieves data and updates it in the opposite direction. The exact direction of stack growth and the order in which bytes are stored are architecture-dependent.

A subroutine call saves a return address and transfers control to a procedure. The return instruction retrieves that address. A well-designed subroutine defines its inputs, outputs, registers modified, flags affected, and stack requirements. These rules form a calling convention. Failure to preserve a caller-required register can cause errors far from the actual subroutine.

In an 8086 procedure, registers may be saved using `PUSH` and restored using `POP`. In ARM, a common convention saves the link register and preserved registers in a stack frame. The stack pointer must remain correctly aligned where the application binary interface requires alignment. Stack imbalance is a serious defect: every push must have a matching pop unless the subroutine deliberately changes the frame structure.

Representative Architectures

The 8085 as an 8-bit Accumulator Architecture

The 8085 has an 8-bit accumulator, general-purpose registers B, C, D, E, H, and L, and register pairs BC, DE, and HL for selected 16-bit operations. The flags include sign, zero, auxiliary carry, parity, and carry. Many arithmetic and logical operations use the accumulator implicitly. The H-L pair can serve as a memory pointer, with the notation M referring to the memory location addressed by HL.

Its instruction set is compact and educationally valuable because data movement, flag generation, register pairing, and memory access are explicit. However, the limited register set can lead to frequent memory transfers. A programmer must also account for instructions that affect only some flags or use implicit operands.

The 8086 as a 16-bit Segmented Architecture

The 8086 contains 16-bit general-purpose registers AX, BX, CX, and DX, index and pointer registers, segment registers, an instruction pointer, and a flag register. AX, BX, CX, and DX can be divided into high and low bytes. Memory addresses are formed through segment and offset information, historically represented by the physical-address relation:

`Physical address = 16 × segment + offset`

This organization permits a larger memory space than a simple 16-bit address register while introducing segment-management issues. The 8086 supports immediate, register, direct, register-indirect, based, indexed, and based-indexed addressing. Its instructions can operate on bytes or words, but source and destination operand sizes must be compatible.

The 8086 also illustrates the importance of assembler directives, labels, procedures, and data declarations. A symbolic program is easier to relocate and maintain than one containing unexplained numeric addresses.

A 32-bit ARM Load/Store Model

A representative 32-bit ARM architecture provides a bank of general-purpose registers and a program counter, with a status register for condition information. Data-processing operations generally use registers, while explicit load and store instructions access memory. This separation supports regular instruction encoding and efficient pipelined implementation.

ARM-style instructions often support shifted register operands, immediate constants, conditional execution in some architectural variants, and block transfer operations. The programmer must distinguish architecture-specific syntax from general principles. For example, `ADD R0, R1, R2` means R0 receives the sum of R1 and R2, whereas `LDR R0, [R1, #4]` loads from the address R1 plus four bytes.

The 32-bit perspective introduces additional concerns: word alignment, byte ordering, application binary interfaces, interrupt modes, and interaction with caches and memory systems. Assembly optimization must therefore consider more than instruction count.

Program Development Method

Assembly programming is most reliable when treated as a disciplined translation process rather than as ad hoc mnemonic writing. First, specify the algorithm using equations, pseudocode, or a flowchart. Second, define the representation of inputs and outputs, including byte width, signedness, memory layout, and scaling. Third, allocate registers and memory locations. Fourth, identify flag dependencies and possible overflow. Fifth, write the program with labels and comments. Finally, assemble, link, simulate or run, and verify with representative test cases.

A useful register-allocation table records each register’s purpose. For example:

| Register | Purpose |

|---|---|

| A or R0 | Current data value |

| B or R1 | Loop counter |

| HL or R2 | Memory pointer |

| C or R3 | Temporary result |

The table prevents accidental reuse of a live value. Comments should explain intent, not merely translate mnemonics. “Mask alarm bit” is more useful than “AND 08H.”

Example: Summing an Array

The following 8085-style routine sums ten unsigned bytes beginning at memory location 3000H. The 16-bit result is stored at 3010H and 3011H, low byte first.

```asm

LXI H, 3000H ; HL points to first element

MVI C, 0AH ; ten elements

MVI A, 00H ; low byte of sum

MVI B, 00H ; high byte of sum

LOOP: ADD M ; add current byte to low byte

JNC NEXT ; no carry: high byte unchanged

INR B ; carry increments high byte

NEXT: INX H ; point to next element

DCR C ; decrement count

JNZ LOOP

STA 3010H ; store low byte

MOV A, B

STA 3011H ; store high byte

HLT

```

The program demonstrates indirect memory access, a counter loop, carry handling, and a 16-bit result constructed from two 8-bit registers. Its limitation is that the high byte is incremented only once per addition; this is correct for adding two 8-bit values to an existing 8-bit low byte because the low-byte addition generates at most one carry. A stronger implementation would document the unsigned assumption and test the maximum possible total.

Example: Searching for a Maximum

For an array of unsigned bytes, the first element can be treated as the current maximum. Each subsequent element is compared with it. If the new element is higher, it replaces the current maximum. The algorithm requires a pointer, a count, a current maximum, and a temporary value. The comparison must use an unsigned condition; a signed branch would produce incorrect results when values above 7FH occur.

The same algorithm on ARM would normally load an element into a register, compare it with the current maximum, conditionally move or branch, advance the pointer by one byte, and decrement the count. The load/store form makes the memory operation explicit, while the algorithm remains unchanged.

Debugging, Verification, and Optimization

Assembly debugging begins with an expected-state table. For each significant instruction, record the program counter, registers, memory values, and flags. Single-step execution is especially useful around comparisons, carries, stack operations, and branches. Breakpoints should be placed at subroutine entry and exit, loop boundaries, and error-handling paths.

Frequent errors include confusing hexadecimal and decimal constants, using the wrong operand size, failing to initialize a register, overwriting a value before its last use, misunderstanding flag effects, using an incorrect branch condition, and failing to preserve a register in a subroutine. Incorrect stack initialization can cause apparently unrelated returns or interrupts to fail.

Optimization should follow measurement or a defensible timing requirement. Possible techniques include keeping frequently used values in registers, reducing unnecessary loads and stores, using suitable addressing modes, replacing repeated operations with shifts where mathematically valid, and organizing loops to minimize overhead. Optimization must not sacrifice correctness. A shift is equivalent to multiplication by two only when overflow, signedness, and scaling requirements permit it. Likewise, removing a seemingly redundant instruction may remove a necessary flag update.

For real-time systems, worst-case execution time matters more than average execution time. Interrupt latency, memory wait states, branch behavior, and disabled-interrupt intervals must be considered. Code size may be more important than speed in a small microcontroller, while energy consumption may favor fewer memory accesses rather than merely fewer instructions.

Applied Case Study: Sensor Threshold Controller

Background

Consider an illustrative embedded controller that samples an 8-bit temperature sensor every millisecond. The sensor value is stored in memory, and an actuator must be enabled when the value is greater than or equal to a programmed threshold. A status bit records whether the actuator is active.

Problem

The controller must compare the unsigned sensor value with the threshold, set or clear one output bit without disturbing other control bits, and execute continuously. A faulty implementation might use a signed comparison or overwrite the entire output port when only one bit should change.

Analysis

The required operations are data loading, unsigned comparison, bit masking, conditional branching, and output storage. If bit 2 controls the actuator, the set mask is 04H. To enable the actuator, the output value is ORed with 04H. To disable it, the value is ANDed with FBH, the complement of 04H in eight bits. The other seven output bits remain unchanged.

Approach

A processor-independent pseudocode sequence is:

```text

sensor <- read sensor byte

control <- read output latch

if sensor >= threshold then

control <- control OR 04H

else

control <- control AND FBH

write control to output latch

```

On an 8085, the comparison can be implemented using subtraction and an unsigned branch, followed by the appropriate mask operation. On ARM, the sensor and threshold are loaded into registers, a compare instruction sets the condition flags, and conditional branches or conditional operations select the mask path. In either case, the programmer must verify whether the comparison instruction modifies the flags required by the subsequent branch.

Outcome

The controller responds correctly to threshold crossings while preserving unrelated output functions. Testing should include values below, equal to, and above the threshold, as well as control bytes with every combination of unrelated bits. Timing analysis must confirm that the sampling and output update complete within one millisecond.

Learning Points

The case demonstrates that an instruction sequence is correct only when data type, flag interpretation, bit preservation, and timing are considered together. It also shows why symbolic constants such as `ACTUATOR_MASK EQU 04H` improve maintainability and reduce errors.

Professional and Ethical Considerations

Assembly programs often control safety-related, medical, industrial, automotive, or power-electronic equipment. A programmer must document assumptions, test boundary conditions, review interrupt interactions, and preserve traceability between requirements and code. Undocumented timing dependencies and hidden register conventions create maintenance and safety risks.

Professional practice includes version control, code review, static analysis where available, reproducible builds, and clear licensing of reused routines. Security is also relevant: incorrect bounds checks, stack misuse, and unvalidated peripheral inputs can enable faults or attacks. In safety-critical environments, coding standards, independent verification, and formal or semi-formal analysis may be required by the application domain.

Current Developments

Modern embedded development commonly combines C or C++ with short assembly routines for startup, context switching, interrupt entry, atomic operations, and specialized optimization. Compiler-generated assembly should be inspected when timing, register use, or memory ordering matters. Integrated development environments now combine assemblers, symbolic debuggers, instruction trace, performance counters, and hardware-in-the-loop testing.

RISC-V has increased interest in open instruction-set design and custom extensions, while ARM continues to dominate many embedded and mobile applications. Contemporary processors also add SIMD, DSP, security, virtualization, and memory-protection features. These developments do not eliminate the need for ISA knowledge; they make understanding the boundary between architectural guarantees and implementation-specific behavior even more important.

Best Practices

1. Define operand width and signedness before writing instructions.

2. Use labels, symbolic constants, and meaningful register conventions.

3. Document flags modified and registers preserved by every subroutine.

4. Keep stack operations balanced and initialize the stack safely.

5. Test zero, maximum, minimum, carry, overflow, and boundary-count cases.

6. Separate algorithmic correctness from processor-specific optimization.

7. Measure execution time and code size rather than assuming an optimization helps.

8. Preserve unrelated bits when controlling shared hardware registers.

9. Use a debugger or simulator to trace flags and memory, not only final output.

10. Review assembly code for maintainability, interrupt safety, and portability.

Chapter Summary

An instruction-set architecture defines the programmer-visible behavior of a processor, including registers, memory access, instruction formats, addressing modes, flags, and control-flow mechanisms. Assembly programming requires more than memorizing mnemonics: the programmer must understand operand size, signedness, flag semantics, register allocation, stack discipline, and timing.

Data-transfer instructions move values; arithmetic instructions operate on them and generate conditions; logical and bit operations manipulate fields; branches and loops control execution; and stack and subroutine instructions organize reusable code. The 8085 illustrates accumulator-oriented 8-bit programming, the 8086 demonstrates 16-bit segmented organization, and ARM illustrates a 32-bit load/store model. Effective development proceeds from algorithm specification through register allocation, coding, assembly, tracing, testing, and measured optimization. In professional systems, correctness, safety, security, documentation, and predictable timing are as important as compact code.

Key Terms

Instruction-set architecture: The programmer-visible specification of processor operations and state.

Microarchitecture: The internal implementation used to realize an instruction-set architecture.

Opcode: The encoded operation field identifying the instruction’s action.

Operand: Data or an address upon which an instruction operates.

Addressing mode: The method used to locate or form an instruction operand.

Accumulator: A register implicitly used by many arithmetic and logical instructions.

Program counter: The register containing the address of the next instruction.

Status flags: Condition bits updated by selected instructions.

Carry flag: A condition indicating carry out or borrow-related status in arithmetic.

Overflow flag: A condition indicating that signed arithmetic exceeded representable range.

Immediate addressing: An addressing method in which the operand value is encoded in the instruction.

Register-indirect addressing: Accessing memory through an address held in a register.

Stack pointer: A register identifying the current top of the stack.

Subroutine: A reusable sequence of instructions invoked by a call and ended by a return.

Calling convention: Rules governing arguments, return values, register preservation, and stack use.

Mask: A bit pattern used to select, set, clear, or test particular bits.

Load/store architecture: An architecture in which memory is accessed explicitly by load and store instructions.

Endianness: The order in which the bytes of a multibyte value are stored in memory.

Worst-case execution time: The maximum execution time relevant to a specified operating condition.

Review Questions

Short Answer Questions

1. What is the difference between an instruction-set architecture and a microarchitecture?

2. Why must carry and overflow be interpreted differently?

3. What is the purpose of an addressing mode?

4. Why is a compare instruction useful even though it does not normally preserve a subtraction result?

5. State two responsibilities of a subroutine’s calling convention.

Long Answer Questions

1. Explain the major classes of assembly instructions and illustrate their use in an embedded program.

2. Compare the 8085 accumulator organization, 8086 segmented organization, and ARM load/store organization.

3. Explain how multi-byte addition is performed on an 8-bit processor.

4. Describe a systematic method for developing and debugging an assembly program.

5. Explain stack operation, subroutine linkage, register preservation, and common stack-related errors.

Analytical / Application Questions

1. Design an assembly-level algorithm to count the number of set bits in an 8-bit input. Identify the registers, loop condition, and flag or bit operations required.

2. An unsigned 16-bit sensor value is compared with a limit, but the program incorrectly uses a signed branch. Analyze the possible failure and propose a correction.

3. A routine controls one bit of an output port while seven other bits control unrelated devices. Develop a masking strategy that changes only the required bit and explain how you would test it.

22. Multiple Choice Questions with Answers

Question 1

Which statement best distinguishes an ISA from a microarchitecture?

A. The ISA specifies only the physical layout of the processor.

B. The ISA defines programmer-visible operations, while microarchitecture defines their internal implementation.

C. The ISA is used only for high-level languages.

D. Microarchitecture specifies symbolic labels and comments.

Correct Answer: B. The ISA defines programmer-visible operations, while microarchitecture defines their internal implementation.

Question 2

Two positive signed 8-bit numbers produce a negative result without an unsigned carry. Which condition is most directly indicated?

A. Zero

B. Parity

C. Signed overflow

D. Auxiliary carry

Correct Answer: C. Signed overflow

Question 3

Which operation is normally required in a load/store architecture before adding a value located in memory?

A. A load into a register

B. A stack pop only

C. A relative branch

D. A rotate through carry

Correct Answer: A. A load into a register

Question 4

For an array of unsigned bytes, which comparison interpretation is appropriate when selecting the larger element?

A. A signed comparison based only on the sign bit

B. An unsigned comparison using the architecture’s appropriate carry/zero conditions

C. A parity comparison

D. A comparison of program-counter values

Correct Answer: B. An unsigned comparison using the architecture’s appropriate carry/zero conditions

Question 5

What is the principal purpose of a compare instruction?

A. To copy a memory block

B. To perform an operation and update conditions without normally retaining the arithmetic result

C. To allocate stack memory permanently

D. To disable all interrupts

Correct Answer: B. To perform an operation and update conditions without normally retaining the arithmetic result

Question 6

A program must set bit 2 of a control byte without changing other bits. Which operation is appropriate?

A. AND with 04H

B. OR with 04H

C. XOR with FBH

D. Complement the entire byte

Correct Answer: B. OR with 04H

Question 7

Which practice most directly prevents a subroutine from corrupting a caller’s preserved register?

A. Using an unconditional jump instead of a call

B. Saving and restoring the register according to the calling convention

C. Clearing the program counter

D. Using only immediate addressing

Correct Answer: B. Saving and restoring the register according to the calling convention

Question 8

In the 8085 array-sum example, why is a separate high byte maintained?

A. Because the accumulator cannot access memory

B. Because repeated 8-bit additions can produce a result larger than eight bits

C. Because the stack stores only high bytes

D. Because logical instructions require two accumulators

Correct Answer: B. Because repeated 8-bit additions can produce a result larger than eight bits

Question 9

Which test is most important for verifying a threshold comparison routine?

A. Only a typical mid-range value

B. Only a value of zero

C. Values below, equal to, and above the threshold

D. Only values with even parity

Correct Answer: C. Values below, equal to, and above the threshold

Question 10

Which optimization principle is most technically sound?

A. Always minimize instruction count regardless of effects.

B. Replace every multiplication with a shift.

C. Optimize after considering correctness, signedness, timing, memory access, and measured requirements.

D. Avoid all comments to reduce code size.

Correct Answer: C. Optimize after considering correctness, signedness, timing, memory access, and measured requirements.

Micro Processor and Micro Controller
Chapter 3 of 15