Computer Organization and Architecture

Chapter 4
Instruction Set Architecture and Assembly Language

Diploma in Computer Architecture and Networking
Computer Organization and Architecture   •   Diploma

Instruction Set Architecture and Assembly Language

Introduction

An instruction set architecture (ISA) is the programmer-visible specification of a processor. It defines the operations a processor can perform, the registers available to software, the representation of instructions, the organization of memory, the addressing modes, and the mechanisms used for procedure calls and interrupts. The ISA forms the boundary between hardware implementation and system software.

A compiler translates a high-level language such as C into assembly language and, ultimately, machine code. Assembly language provides symbolic names for opcodes, registers, labels, and memory locations. Machine code is the binary encoding executed directly by the processor. Understanding this translation is essential for diagnosing low-level errors, evaluating processor designs, writing embedded programs, and understanding how operating systems control hardware.

Learning Objectives

After studying this chapter, you should be able to:

  • explain the relationship between ISA, assembly language, and machine code;
  • identify opcodes, operands, registers, instruction formats, and addressing modes;
  • trace arithmetic, logical, branch, stack, and procedure instructions;
  • explain calls, returns, interrupts, and the role of the stack;
  • interpret a simple machine-code encoding;
  • compare RISC, CISC, and open ISAs such as RISC-V;
  • write and evaluate short assembly-language routines using sound programming practices.

1. The Programmer-Visible Processor

An ISA specifies what software can assume about a processor, not how the processor is internally built. Two processors may implement the same ISA using different pipelines, cache organizations, or semiconductor technologies. If both produce the same architecturally visible results, they are compatible at the ISA level.

The principal elements of an ISA are:

  1. Data types and sizes: for example, 8-bit bytes, 32-bit words, 64-bit doublewords, signed integers, unsigned integers, and floating-point values.
  2. Registers: fast storage locations directly named by instructions.
  3. Instruction set: arithmetic, logical, data-transfer, control-flow, and system operations.
  4. Memory model: address size, alignment rules, byte ordering, and permitted memory operations.
  5. Instruction formats: the position and size of opcode, register fields, immediate values, and function fields.
  6. Exceptions and interrupts: events that transfer control to privileged software.
  7. Execution and privilege rules: restrictions that distinguish user programs from operating-system code.

The instruction cycle is commonly described as fetch, decode, operand access, execute, memory access, and write-back. Modern processors overlap these activities through pipelining, but the architectural effect remains equivalent to an ordered execution of instructions, subject to the ISA's rules.

The program counter (PC), also called the instruction pointer in some architectures, identifies the address of the next instruction. A status or flags register may record conditions such as zero, negative, carry, and overflow. Not every ISA uses a flags register; RISC-V, for example, generally represents comparison results in general-purpose registers and does not provide a conventional condition-code register.

2. Instructions, Opcodes, and Operands

An instruction consists conceptually of an opcode and one or more operands. The opcode specifies the operation, such as add or load. Operands specify the data or locations involved.

For example, in the symbolic instruction:

ADD R1, R2, R3

ADD is the mnemonic for the opcode, while R1, R2, and R3 are operands. In a typical three-register interpretation, the operation is:

R1 ← R2 + R3

The arrow means that the result replaces the previous contents of R1. The exact operand order is architecture-dependent; assembly programmers must consult the relevant ISA manual.

Instructions usually belong to the following classes:

ClassTypical purposeExamples
Data transferMove data between registers and memoryLOAD, STORE, MOV
ArithmeticPerform numerical operationsADD, SUB, MUL
Logical/bitManipulate bits or test conditionsAND, OR, XOR, SHIFT
Control transferChange the PCJUMP, BRANCH, CALL, RETURN
SystemRequest privileged or operating-system serviceTRAP, ECALL, INTERRUPT RETURN

A register operand names a register. An immediate operand is a constant encoded in the instruction. A memory operand identifies a location whose address must be calculated. A label is a symbolic name converted by the assembler into an address or displacement.

Instruction formats

A fixed-length ISA may use one or more standard layouts. A simplified format might contain a 6-bit opcode, two 5-bit register fields, and a 16-bit immediate field. The field widths determine how many operations, registers, or constants can be represented. In general, a field of n bits can represent 2ⁿ distinct bit patterns, although some patterns may be reserved.

Fixed-length formats simplify instruction fetching and decoding. Variable-length formats can improve code density because frequently used instructions need not occupy the same number of bytes. They can, however, make decoding and instruction alignment more complex.

Machine-code interpretation

Suppose an illustrative 16-bit processor defines:

[ opcode: 4 bits ][ destination: 4 bits ][ source: 4 bits ][ unused: 4 bits ]

If the binary instruction is 0011 0001 0010 0000, and opcode 0011 means ADD, destination 0001 identifies R1, and source 0010 identifies R2, the instruction may be decoded as ADD R1, R2. This is an illustrative encoding, not a universal machine-code format. Real encodings must always be interpreted using the processor's architecture manual.

3. Registers and the Register File

Registers provide low-latency storage for operands, addresses, counters, and intermediate results. Common architectural registers include:

  • General-purpose registers (GPRs): used for integer data and addresses.
  • Program counter (PC): contains the next instruction address.
  • Stack pointer (SP): identifies the top or current boundary of the stack.
  • Frame pointer (FP): provides a stable reference to a procedure's activation record.
  • Status register: stores flags or control bits where supported.
  • Floating-point and vector registers: hold specialized numerical data.
  • Control and system registers: manage memory protection, interrupts, and privilege modes.

A register file with n registers requires a register-identification field wide enough to distinguish all registers; at least log2 n bits are required. For example, 32 registers require 5-bit register identifiers. This describes encoding capacity, not necessarily the number of physical storage elements in a superscalar implementation.

A key distinction is between caller-saved and callee-saved registers. A caller-saved register may be overwritten by a called procedure, so the caller must preserve its value if needed. A callee-saved register must be restored by the called procedure before returning. These conventions are part of an application binary interface (ABI), not necessarily fundamental ISA rules.

4. Addressing Modes

An addressing mode specifies how an instruction obtains an operand or calculates an effective address.

Addressing modeMeaningExample concept
ImmediateConstant is in the instructionADD R1, R1, #5
RegisterOperand is in a registerADD R1, R2, R3
Direct/absoluteInstruction contains a memory addressLOAD R1, [1000]
Register indirectRegister contains the addressLOAD R1, [R2]
Base plus displacementEffective address is base plus constantLOAD R1, 8(R2)
IndexedBase and index contribute to addressLOAD R1, (R2,R3)
PC-relativeAddress is relative to the PCBRANCH label
Auto-increment/decrementAddress register changes during accesscommon in some CISC ISAs
ImpliedOperand is implicitRETURN, or accumulator operations

The general effective-address relationship for base-plus-displacement addressing is:

EA = B + d

Here, EA is the effective byte address, B is the base-register value, and d is a signed displacement expressed in bytes. The equation applies when the ISA defines a linear byte-addressed memory and the displacement is interpreted in byte units. If an instruction addresses words rather than bytes, scaling may be required.

For an array of elements of size s bytes, the address of element i beginning at base address A is:

address(A[i]) = A + i × s

The index must be interpreted according to the array's layout and bounds. A processor generally does not automatically check that i is within the array; such checking is normally the responsibility of software or a higher-level runtime.

Endianness and alignment

In a multi-byte value, big-endian systems store the most significant byte at the lowest address, while little-endian systems store the least significant byte at the lowest address. Endianness affects interpretation when bytes are exchanged between systems or accessed individually.

Alignment means placing an object at an address suitable for its size or type. Some processors permit unaligned access with a performance cost; others generate an exception. Portable systems software should follow the ISA and ABI alignment rules rather than assuming that every address is valid for every data type.

5. Assembly Language Programming

An assembler converts symbolic assembly into machine code. A typical source file contains labels, instructions, directives, and comments:

.text
        .global sum_to_n
sum_to_n:
        li   t0, 0          # sum = 0
        li   t1, 1          # i = 1
loop:
        bgt  t1, a0, done   # if i > n, finish
        add  t0, t0, t1     # sum += i
        addi t1, t1, 1      # i++
        j    loop
done:
        mv   a0, t0         # return value
        ret

This example uses RISC-V-style pseudoinstructions and register names. li, mv, bgt, j, and ret may be assembler conveniences that expand into one or more real ISA instructions. The routine assumes that the input n is in a0 and that the result is returned in a0, following a common RISC-V calling convention.

For n ≥ 1, the routine computes the sum 1 + 2 + … + n. The number of loop iterations is n, so its time complexity is O(n). A mathematical alternative is:

S = n × (n + 1) ÷ 2

where S is the sum and n is a non-negative integer. The formula is useful for verification, but replacing the loop with the formula requires multiplication and careful handling of integer overflow.

Tracing a program

To trace assembly, create a table containing the PC, relevant registers, memory locations, and branch result after each instruction. For example, with n = 3, the loop above evolves as follows:

Stagei (t1)sum (t0)Action
Initial10initialize
121add 1, increment
233add 2, increment
346add 3, increment
Finish464 > 3, branch to done

Tracing reveals both the final result and possible errors such as an incorrect initial value, an inverted branch condition, or a missing increment.

6. Arithmetic, Logic, and Conditions

Integer arithmetic is performed in a fixed number of bits. For an n-bit unsigned value, the representable range is 0 through 2ⁿ − 1. For two's-complement signed integers, the range is:

−2^(n−1) ≤ x ≤ 2^(n−1) − 1

where x is the signed value and n is the number of bits. Arithmetic that produces a result outside the range may wrap modulo 2ⁿ for unsigned operations. Signed overflow has a different interpretation and may be detected by a flag, a special instruction, or software comparison, depending on the ISA.

Logical instructions operate on bit patterns. AND can clear selected bits, OR can set selected bits, and XOR can toggle selected bits. A mask is a bit pattern used to select or modify particular positions. Left and right shifts move bits; the treatment of bits shifted into the result depends on whether the shift is logical or arithmetic.

A comparison may set condition flags or produce a Boolean result. Branch instructions then use that condition. Signed and unsigned comparisons are not interchangeable: the same bit pattern can represent different numerical values under the two interpretations.

7. Stacks, Procedures, and Recursion

A stack is a last-in, first-out (LIFO) data structure. A PUSH operation places a value on the stack; POP removes the most recently placed value. Some ISAs provide explicit push and pop instructions, while others implement stack operations using ordinary stores, loads, and updates to the stack pointer.

A procedure call normally performs four logical tasks:

  1. preserve a return address;
  2. transfer control to the procedure;
  3. establish any required stack frame and preserve registers;
  4. restore the frame and return to the saved address.

A stack frame, or activation record, may contain the return address, saved registers, local variables, spilled temporary values, and procedure arguments. The exact layout is defined by the ABI and compiler convention.

A procedure must preserve the stack pointer according to the ABI. Failure to restore it can corrupt the caller's data and cause a later return or memory access to fail. Recursion is possible because each invocation receives a separate activation record, but excessive recursion can exhaust stack space.

Illustrative procedure

factorial:
        addi sp, sp, -16       # reserve frame
        sw   ra, 12(sp)        # save return address
        sw   a0, 8(sp)         # save n
        li   t0, 1
        ble  a0, t0, base
        addi a0, a0, -1
        call factorial
        lw   t1, 8(sp)
        mul  a0, a0, t1
base:
        lw   ra, 12(sp)
        addi sp, sp, 16
        ret

This is illustrative and assumes suitable RISC-V instructions, an ABI-compatible stack frame, and no omitted register-preservation requirements. It returns n! for a suitable non-negative integer n. A production routine must also consider overflow, invalid inputs, instruction availability, and the precise ABI.

8. Interrupts, Exceptions, and Privilege

An exception is a synchronous event caused by the current instruction, such as an invalid opcode, arithmetic fault, or page fault. An interrupt is generally an asynchronous event generated by hardware, such as a timer or network device. A trap is a general term often used for a controlled transfer to privileged software, although terminology varies among architectures.

When an event occurs, the processor typically:

  1. records sufficient context, including a return PC and cause information;
  2. changes privilege state if required;
  3. selects a handler address from a vector or trap configuration;
  4. executes the handler;
  5. restores context and returns using a special instruction.

The exact saved state varies. Some processors save registers in hardware; others use a short hardware entry sequence followed by software register saving. Interrupt handlers must be short, preserve required state, acknowledge the device when appropriate, and avoid unsafe operations unless the operating system permits them.

Privilege separation protects the system from faulty or malicious programs. User-mode code should not directly modify page tables, disable interrupts, or access device-control registers. Operating systems expose controlled interfaces such as system calls. Secure interrupt design also requires validating device data, preventing unauthorized handler entry, and considering timing and denial-of-service attacks.

9. RISC, CISC, and Open ISAs

RISC architectures traditionally emphasize a relatively small set of simple instructions, regular fixed-length formats, many registers, and load/store access to memory. These properties can simplify decoding and pipelining. CISC architectures traditionally provide a larger and more varied instruction set, including complex memory operands and variable-length instructions. Such instructions may improve code density but complicate decoding and implementation.

The distinction is a design tendency rather than a perfect binary classification. Modern CISC processors may translate complex instructions into internal micro-operations, while modern RISC processors may include multiplication, vector operations, compressed encodings, and other sophisticated features.

RISC-V is an open standard ISA developed through an open specification and a modular design. A base integer ISA may be extended with modules for multiplication and division, atomic operations, floating point, compressed instructions, vectors, and other functions. “Open ISA” means that the specification can be implemented without the traditional need to license a proprietary instruction-set definition; it does not mean that every implementation, chip design, tool, or software component is free of all licensing restrictions.

CharacteristicTraditional RISC tendencyTraditional CISC tendencyRISC-V example
Instruction lengthOften fixedOften variableBase formats are regular; compressed extension is optional
Memory operationsUsually load/storeMay operate directly on memoryLoad/store architecture
RegistersRelatively numerousHistorically fewer, though modern designs vary32 integer registers in common base variants
Control flowExplicit branches and jumpsMay include rich addressing and call formsBranches compare registers and branch directly
ExtensibilityUsually controlled extensionsOften accumulated over timeModular standard extensions
AvailabilityMay be proprietary or licensedOften proprietaryOpen specification

The correct choice depends on performance, power, code density, tool support, compatibility, security, development cost, and ecosystem maturity—not on the label RISC or CISC alone.

10. Applied Case: Diagnosing a Faulty Embedded Routine

Background

An illustrative embedded controller reads eight sensor values from consecutive 16-bit memory locations and must calculate their sum. The software occasionally reports a value much larger than expected.

Problem

The routine uses a base address in a0, a loop counter in t0, and an accumulator in t1. A review finds:

li   t0, 0
        li   t1, 0
loop:  lw   t2, 0(a0)
        add  t1, t1, t2
        addi a0, a0, 2
        addi t0, t0, 1
        blt  t0, 8, loop

Analysis

There are several possible faults. First, the data is specified as 16-bit values, but lw loads a word. On an architecture where a word is 32 bits, the instruction may read adjacent sensor data or violate alignment. Second, the branch syntax may be invalid or may not compare an immediate in the chosen ISA. Third, the code does not specify whether sensor values are signed or unsigned, nor whether the accumulator can represent the maximum possible sum.

If each sensor value is an unsigned 16-bit value, the largest sum is:

8 × (216 − 1) = 524280

This fits in 32 bits but not in a signed 16-bit accumulator. The correct load should be a halfword load with the appropriate sign extension or zero extension. The loop condition should use a register containing the bound or a valid pseudoinstruction expansion.

Approach and solution

A corrected conceptual version is:

li   t0, 0              # index = 0
        li   t1, 0              # 32-bit sum
        li   t3, 8              # count
loop:  lhu  t2, 0(a0)           # unsigned 16-bit load
        add  t1, t1, t2
        addi a0, a0, 2          # next 16-bit element
        addi t0, t0, 1
        bltu t0, t3, loop
        mv   a0, t1

This assumes valid alignment, a 32-bit accumulator, and an ABI in which a0 carries the result. If signed sensor values are intended, lh rather than lhu may be required. Production code should also validate the address range and handle sensor or bus errors.

Outcome and learning points

The defect was not simply an arithmetic error. It involved data width, extension rules, address increments, branch semantics, and range analysis. The case demonstrates why assembly debugging requires simultaneous attention to the ISA, data representation, ABI, and application requirements.

11. Limitations, Best Practices, and Contemporary Practice

Assembly offers precise control and can be valuable for boot code, interrupt handlers, device drivers, cryptographic primitives, and performance-critical kernels. It is, however, more difficult to port, test, verify, and maintain than a high-level language. Compiler-generated assembly should be inspected with knowledge of optimization, ABI conventions, and debugging metadata.

Recommended practices include:

  • document register assumptions and calling conventions;
  • use symbolic labels instead of hard-coded addresses where possible;
  • distinguish signed and unsigned operations explicitly;
  • check alignment, bounds, and integer overflow;
  • preserve callee-saved registers and restore the stack pointer;
  • use a debugger or simulator to inspect PC, registers, memory, and control flow;
  • verify pseudoinstructions by examining their real expansions;
  • test boundary cases, including zero, maximum values, negative values, and invalid addresses;
  • avoid embedding secrets in registers or memory without considering privilege, debugging, and side-channel risks.

Contemporary processors increasingly combine scalar instructions with SIMD/vector extensions, hardware virtualization, memory protection, cryptographic instructions, and security features. RISC-V's modular extensions illustrate how an ISA can evolve while retaining a stable base. Nevertheless, new extensions must be evaluated for compatibility, verification cost, compiler support, power consumption, and security consequences.

Chapter Summary

An ISA defines the programmer-visible contract of a processor. Its key elements include operations, registers, data types, memory rules, instruction formats, addressing modes, privilege levels, and interrupt behavior. Assembly language gives symbolic form to machine instructions, while the assembler converts that form into encoded binary.

Opcodes identify operations and operands identify registers, constants, or memory locations. Effective addresses are often calculated from a base register and displacement. Endianness and alignment affect how data is stored and accessed. Stacks support procedure calls, local storage, saved registers, and recursion. Interrupts and exceptions transfer control to privileged handlers while preserving sufficient processor context.

RISC and CISC describe broad architectural tendencies rather than absolute categories. RISC-V demonstrates an open, modular ISA whose extensions can support embedded, general-purpose, vector, and specialized computing. Reliable assembly programming requires careful tracing, explicit data-width choices, ABI compliance, range analysis, and disciplined testing.

Key Terms

ISA: The programmer-visible specification of a processor.

Opcode: The encoded operation field of an instruction.

Operand: A value, register, constant, or location used by an instruction.

Mnemonic: A symbolic name for an instruction operation.

Machine code: Binary instruction encoding executed by a processor.

Assembly language: Symbolic representation of machine instructions.

Register: Small, fast storage location directly named by instructions.

Program counter: Register containing the address of the next instruction.

Stack pointer: Register identifying the current stack boundary.

Addressing mode: Rule used to locate or calculate an operand.

Effective address: Computed memory address used by an instruction.

Immediate: Constant encoded within an instruction.

ABI: Convention defining binary-level interfaces between software components.

Stack frame: Procedure-specific region containing saved state and local data.

Interrupt: Generally asynchronous hardware-generated control event.

Exception: Synchronous event caused by instruction execution.

Privilege mode: Processor operating level controlling access to protected resources.

RISC: Reduced Instruction Set Computer design tendency.

CISC: Complex Instruction Set Computer design tendency.

RISC-V: An open, modular instruction-set architecture.

Review Questions

Short Answer Questions

  1. What is the difference between an ISA and a processor implementation?
  2. Define opcode, operand, and immediate operand.
  3. What is the purpose of a stack pointer?
  4. Distinguish an interrupt from an exception.
  5. Why must signed and unsigned comparisons be distinguished?

Long Answer Questions

  1. Explain the fetch-decode-execute sequence and relate each stage to the program counter, registers, and memory.
  2. Compare immediate, register-indirect, base-plus-displacement, and PC-relative addressing modes with examples.
  3. Explain how a procedure call uses a return address, stack frame, saved registers, and calling convention.
  4. Compare traditional RISC and CISC design tendencies, including their effects on decoding, code density, and implementation.
  5. Describe the main features of RISC-V and explain the significance of its modular and open nature.

Analytical / Application Questions

  1. A routine loads eight 16-bit unsigned values but uses a word load and advances the address by four bytes. Identify the errors and propose corrections.
  2. Trace a loop that starts with i = 1, adds i to an accumulator, increments i, and stops when i > 5. State the final accumulator and explain how you verified it.
  3. Design a procedure interface for summing an array. Specify the argument registers, result register, registers that must be preserved, and the checks required for element size, alignment, and overflow.

Multiple Choice Questions with Answers

Question 1

Which component of an instruction specifies the operation to be performed?

A. Opcode

B. Stack pointer

C. Displacement

D. Status flag

Correct Answer: A. Opcode

Question 2

For base-plus-displacement addressing, what is the effective address?

A. Base register minus the program counter

B. Base register plus the displacement

C. Displacement divided by the word size

D. Program counter plus the stack pointer

Correct Answer: B. Base register plus the displacement

Question 3

How many bits are required to identify one of 32 registers?

A. 3

B. 4

C. 5

D. 6

Correct Answer: C. 5

Question 4

What is the defining behavior of a stack?

A. First-in, first-out

B. Last-in, first-out

C. Random replacement

D. Circular addressing only

Correct Answer: B. Last-in, first-out

Question 5

Which event is normally synchronous with the instruction being executed?

A. Timer interrupt

B. Network interrupt

C. Keyboard interrupt

D. Invalid-opcode exception

Correct Answer: D. Invalid-opcode exception

Question 6

In a load/store architecture, which instruction normally accesses memory?

A. Only load and store instructions

B. Every arithmetic instruction

C. Only branch instructions

D. Only instructions using immediate operands

Correct Answer: A. Only load and store instructions

Question 7

What is the largest possible sum of eight unsigned 16-bit values?

A. 65535

B. 131070

C. 524280

D. 1048576

Correct Answer: C. 524280

Question 8

Which statement best describes RISC-V?

A. A fixed proprietary processor implementation

B. An open, modular instruction-set architecture

C. A programming language for device drivers

D. A memory-allocation algorithm

Correct Answer: B. An open, modular instruction-set architecture

Question 9

What is the primary purpose of a procedure's return address?

A. To identify the next instruction in the called procedure

B. To identify where execution resumes in the caller

C. To store the procedure's first argument

D. To specify the stack size

Correct Answer: B. To identify where execution resumes in the caller

Question 10

Why can a word load be incorrect for a 16-bit sensor array?

A. It always reverses endianness

B. It may read more bytes than one sensor element

C. It cannot use a base address

D. It automatically prevents overflow

Correct Answer: B. It may read more bytes than one sensor element

Computer Organization and Architecture
Chapter 4 of 14