Previous | Tutorial index | Next
Tutorial 3: Basic Principles of Modern Computers
Learning Objective
To be able to explain the basic principles of modern computers.
3.1 Introduction: The Foundations of Computing
While Tutorial 2 described the physical parts of a computer, this tutorial explains the intellectual and mathematical principles that make those parts work. These principles are universal—whether you are using a supercomputer, a smartphone, or a smartwatch, they all obey the same fundamental rules. Understanding these concepts is essential for any programmer because software is ultimately constrained by these hardware realities. This tutorial covers the von Neumann architecture, binary logic, the execution cycle of the CPU, and the layers of abstraction that allow us to write high-level Python code instead of flipping switches manually.
3.2 The Von Neumann Architecture: The Blueprint of Modern PCs
In 1945, mathematician and physicist John von Neumann wrote a report describing the architecture of the EDVAC (Electronic Discrete Variable Automatic Computer). This report formalized the design that virtually all modern general-purpose computers follow today.
The Core Concept: Stored-Program
Before von Neumann, computers like ENIAC were programmed by physically rewiring cables and setting switches. Changing a program meant physically rebuilding the machine. Von Neumann's revolutionary idea was the stored-program concept:
Both the program's instructions and the data the program operates on are stored in the same read/write memory (RAM).
This is the defining feature of the von Neumann architecture. It means the computer can treat instructions like data, allowing programs to modify themselves or other programs (which is how compilers and operating systems work).
The Four Major Subsystems
The von Neumann architecture divides a computer into four main functional units:
- Memory (RAM): Stores both data and instructions in numbered locations (addresses).
- Processing Unit (CPU): Executes instructions. Contains the ALU and Control Unit.
- Input: Devices that bring data and programs into the system (keyboard, mouse, network card).
- Output: Devices that send processed data out (monitor, printer, network card).
The "Von Neumann Bottleneck"
Because both instructions and data share the same memory and the same system bus (the pathway connecting CPU to RAM), only one piece of information (either an instruction or a data value) can be fetched from memory at a time. This single path creates a bottleneck, limiting the overall speed of the computer. The CPU is often much faster than the memory bus, so it spends a significant amount of time waiting for data.
Harvard Architecture (A Brief Contrast)
In contrast, the Harvard architecture uses physically separate memory and buses for instructions and data. This allows fetching an instruction and a data value simultaneously. This is commonly used in microcontrollers (like Arduino) and digital signal processors, but modern desktop CPUs actually use a hybrid approach: they have separate L1 caches for instructions and data (Harvard internally), but a unified memory system externally (von Neumann). This gives them the speed of Harvard with the flexibility of von Neumann.
3.3 The Binary System: The Language of Computers
Why do computers use binary (base-2) instead of decimal (base-10)? Because hardware is fundamentally electrical. Transistors and logic gates have two stable states: ON (electricity flowing) or OFF (no electricity flowing). It is cheap and reliable to distinguish between two voltage levels (e.g., 0 volts vs. 5 volts), but very difficult and expensive to distinguish between ten distinct voltage levels accurately.
Bits and Bytes
- Bit (b): The smallest unit of data. Represents a binary digit: either a
0 (OFF) or a 1 (ON).
- Nibble: 4 bits (e.g.,
1011).
- Byte (B): 8 bits (e.g.,
11001010). This is the fundamental unit of memory addressing. A memory address points to a single byte.
- Word: The natural unit of data for a given CPU architecture. On a 64-bit system, a word is 8 bytes (64 bits). This determines how much data the CPU can process in one operation.
Binary to Decimal Conversion
In decimal (base-10), the number 345 means 3*10^2 + 4*10^1 + 5*10^0.
In binary (base-2), each position represents a power of 2 (128, 64, 32, 16, 8, 4, 2, 1).
Example: Convert binary 100101 to decimal:
(1 * 2^5) + (0 * 2^4) + (0 * 2^3) + (1 * 2^2) + (0 * 2^1) + (1 * 2^0)
= 32 + 0 + 0 + 4 + 0 + 1 = 37 in decimal.
Decimal to Binary Conversion (Division Method)
To convert decimal 41 to binary:
- Divide 41 by 2 → Quotient 20, Remainder 1 (Least Significant Bit)
- Divide 20 by 2 → Quotient 10, Remainder 0
- Divide 10 by 2 → Quotient 5, Remainder 0
- Divide 5 by 2 → Quotient 2, Remainder 1
- Divide 2 by 2 → Quotient 1, Remainder 0
- Divide 1 by 2 → Quotient 0, Remainder 1 (Most Significant Bit)
Read backwards:
101001. (Check: 32+8+1 = 41).
Prefixes: Base-2 vs. Base-10 Confusion
- Metric (Decimal) Prefixes: KB (Kilobyte = 1000 bytes), MB (Megabyte = 1,000,000 bytes) - used by hard drive manufacturers.
- Binary Prefixes: KiB (Kibibyte = 1024 bytes), MiB (Mebibyte = 1,048,576 bytes) - used by operating systems like Windows and Linux when reporting memory.
In this course, we will use the binary convention (1 KB = 1024 bytes) when referring to memory sizes, unless explicitly stated otherwise.
Why Hexadecimal?
Binary is hard for humans to read (e.g., 1011001110101110). Hexadecimal (base-16) is used as a shorthand. Since 16 = 2^4, one hex digit perfectly represents a nibble (4 bits).
0 = 0000, 1 = 0001, ... A = 1010, F = 1111.
- Example:
1010 1110 0011 = A E 3 in hex.
3.4 The Fetch-Decode-Execute Cycle: The Heartbeat of the CPU
The CPU operates continuously, performing billions of cycles per second (measured in Gigahertz). Each cycle consists of three main stages.
The Registers Involved
To understand the cycle, you must know these internal CPU registers:
- PC (Program Counter): Holds the memory address of the next instruction to execute.
- MAR (Memory Address Register): Holds the address of the memory location to be read from or written to.
- MDR (Memory Data Register): Holds the actual data or instruction fetched from memory.
- IR (Instruction Register): Holds the current instruction being decoded and executed.
- Accumulator: A general-purpose register that holds the result of the last ALU operation.
Step-by-Step Walkthrough
Imagine a simple instruction: ADD 5 (Add the value 5 to a number in a register).
-
Fetch:
- The CPU reads the address stored in the PC.
- It copies that address into the MAR.
- The memory bus locates that address in RAM.
- The instruction
ADD 5 travels back via the bus and is stored in the MDR.
- The instruction is then moved from the MDR to the IR.
- PC is incremented to point to the next instruction in memory.
-
Decode:
- The Control Unit (CU) examines the binary pattern in the IR.
- It decodes the opcode (operation code).
ADD means "tell the ALU to perform an addition."
- It determines that the operand
5 is required. If the operand is in memory, the CU might initiate another fetch; but if it's an immediate value, it is part of the instruction itself.
-
Execute:
- The Control Unit sends control signals to the ALU.
- The ALU takes the current value from a register (e.g., Accumulator) and adds the number 5 to it.
- The result is stored back into the accumulator.
- The cycle starts over with the next instruction pointed to by the PC.
Clock Speed and Pipelining
- Clock Speed (GHz): The CPU's internal "metronome" that synchronizes operations. A 3.0 GHz CPU has 3 billion cycles per second.
- Pipelining: Instead of executing one instruction completely before starting the next (which leaves parts of the CPU idle), modern CPUs use pipelining. While one instruction is in the Execute stage, the next is in the Decode stage, and the next is being Fetched. This massively improves throughput, though it introduces complexity (hazards) if the next instruction depends on the result of the current one.
3.5 Levels of Abstraction: From Logic Gates to Python
No human works directly with binary machine code. We use layers of abstraction to manage complexity. Each layer translates the layer above into a language the layer below understands.
| Layer |
Description |
Example |
| 1. Problem |
The real-world task you want to solve. |
"I want to calculate the average temperature." |
| 2. Algorithm |
A step-by-step logical solution. |
Pseudocode: Sum all temps, divide by count. |
| 3. High-Level Language |
Human-readable programming code. |
average = sum(temps) / len(temps) (Python) |
| 4. Assembly Language |
Human-readable mnemonic for machine code. |
MOV R1, #10
ADD R2, R1 |
| 5. Machine Code (ISA) |
The specific binary instructions for that CPU. |
10110000 00001010 |
| 6. Microarchitecture |
How the CPU hardware implements the ISA. |
Control signals, ALU paths, cache logic. |
| 7. Digital Logic |
Physical circuits (AND, OR, NOT gates). |
Transistors switching on/off. |
The Interpreter/Compiler Role:
When you write Python, the Python Interpreter (which is itself a program written in C) translates your Python code down to Bytecode, and then the Python Virtual Machine translates that into machine code on the fly. This extra layer makes Python slower than compiled languages (like C) but much easier and safer to use.
Why Abstraction Matters:
It allows us to think about what we want to do (the problem), rather than how the silicon does it. We can write print("Hello") without worrying about memory registers, bus signaling, or voltage levels.
Since instructions are stored in the same memory as data, the CPU does not intrinsically know the difference—it just sees a sequence of bytes. It is the context (specifically, the PC pointing to it) that treats it as code.
Implications:
- Self-modifying code: A program can write new instructions into memory and then jump to them. Historically used for performance hacks, but today it is discouraged (and often prevented by modern OS security) because it leads to unpredictable bugs.
- Compilers and Assemblers: A compiler is a program that reads human-readable text (data) and writes executable machine code (also data) to a file on your disk.
- Security: Hackers can exploit this by injecting malicious code as "data" (e.g., via a network packet) and tricking the CPU into executing it as "instructions." This is why modern CPUs have mechanisms like NX (No-Execute) bits to mark memory areas as data-only.
3.7 Summary Table
| Principle |
Core Idea |
Why It Matters |
| Von Neumann Architecture |
Instructions & Data share a single memory space. |
Enables programmability and self-modification; creates the bus bottleneck. |
| Binary System |
Everything boils down to 0s and 1s based on voltage levels. |
Determines data representation; affects memory capacity and precision. |
| Fetch-Decode-Execute |
CPU repeats this cycle billions of times per second. |
Defines the fundamental operational rhythm of all programs. |
| Levels of Abstraction |
Layers of translation hide complexity. |
Allows us to write high-level Python instead of binary. |
3.8 Quizzes
Quiz 1: Von Neumann Architecture
1. What is the defining characteristic of the von Neumann architecture?
- (A) It uses vacuum tubes instead of transistors.
- (B) It stores both instructions and data in the same memory.
- (C) It separates memory into distinct banks for high-speed access.
- (D) It requires no input or output devices.
Answer
(B) It stores both instructions and data in the same memory.
2. What limitation arises from sharing a single bus for both instructions and data?
- (A) The Harvard bottleneck
- (B) The von Neumann bottleneck
- (C) The ALU bottleneck
- (D) The software bottleneck
Answer
(B) The von Neumann bottleneck
3. Which subsystem of the von Neumann model is responsible for executing instructions?
- (A) Memory
- (B) Input
- (C) Processing Unit (CPU)
- (D) Output
Answer
(C) Processing Unit (CPU)
Quiz 2: Binary Systems
4. Convert the binary number 1101 to decimal.
- (A) 13
- (B) 11
- (C) 15
- (D) 10
Answer
(A) 13 (8+4+1)
5. Convert the decimal number 23 to binary.
- (A)
10110
- (B)
10111
- (C)
11011
- (D)
11110
Answer
(B) `10111` (16+4+2+1)
6. How many bytes are in 1 Kibibyte (KiB)?
- (A) 1000
- (B) 1024
- (C) 2048
- (D) 8192
Answer
(B) 1024
7. What is the hexadecimal representation of the binary number 1111 0001?
- (A) F1
- (B) 1F
- (C) 0F
- (D) FF
Answer
(A) F1
Quiz 3: Fetch-Decode-Execute
8. Which register holds the memory address of the next instruction to be executed?
- (A) Instruction Register (IR)
- (B) Memory Data Register (MDR)
- (C) Program Counter (PC)
- (D) Accumulator
Answer
(C) Program Counter (PC)
9. During which stage does the Control Unit determine what operation to perform (e.g., ADD vs. SUB)?
- (A) Fetch
- (B) Decode
- (C) Execute
- (D) Store
Answer
(B) Decode
10. Modern CPUs use pipelining to:
- (A) Increase the physical clock speed (GHz).
- (B) Overlap different stages of multiple instructions.
- (C) Store more data in the hard drive.
- (D) Connect to the internet faster.
Answer
(B) Overlap different stages of multiple instructions.
Quiz 4: Abstraction and Stored-Program
11. Which layer sits directly above Machine Code in the abstraction hierarchy?
- (A) Digital Logic
- (B) Algorithm
- (C) Microarchitecture
- (D) Assembly Language
Answer
(D) Assembly Language
12. The fact that "instructions are data" means that:
- (A) You cannot run compiled programs.
- (B) A program can write data to memory and then execute that data as code.
- (C) Python code cannot be compiled.
- (D) Memory is faster than the CPU.
Answer
(B) A program can write data to memory and then execute that data as code.
13. The Python interpreter primarily translates Python code into:
- (A) Direct machine code via a compiler
- (B) Assembly language only
- (C) Bytecode, which is then interpreted or compiled JIT
- (D) Physical transistor switches
Answer
(C) Bytecode, which is then interpreted or compiled JIT
3.9 Exercises
Exercise 1: Binary and Hexadecimal Conversion Drills
Instructions: Perform the following conversions. Show your work for full understanding.
- Binary to Decimal: Convert
10101010 to decimal.
- Decimal to Binary: Convert
156 to binary (use the division method).
- Binary to Hex: Convert
1100 1011 0110 to hexadecimal.
- Hex to Binary: Convert the hex value
3F7A to binary.
- Decimal to Hex: Convert
243 to hexadecimal (hint: convert to binary first, then group into nibbles).
Answer Key
1. `10101010` = 128 + 32 + 8 + 2 = **170**.
2. 156/2=78 R0, 78/2=39 R0, 39/2=19 R1, 19/2=9 R1, 9/2=4 R1, 4/2=2 R0, 2/2=1 R0, 1/2=0 R1. Backwards: `10011100`.
3. `1100 1011 0110` = C B 6 = `CB6`.
4. `3` = 0011, `F`=1111, `7`=0111, `A`=1010. Result: `0011111101111010`.
5. 243 to binary: 128+64+32+16+2+1 = `11110011`. Group: `1111 0011` = `F3`.
Exercise 2: Tracing the Fetch-Decode-Execute Cycle
Instructions: Suppose the following simple instructions are stored sequentially in memory starting at address 0x100. The Program Counter starts at 0x100.
- Address
0x100: LOAD #5 (Load the number 5 into the accumulator)
- Address
0x101: ADD #3 (Add the number 3 to the accumulator)
- Address
0x102: STORE 0x200 (Store the accumulator value into memory address 0x200)
Write a step-by-step trace showing:
- What is the value of the PC at the start of each instruction?
- What does the IR hold during the Decode stage?
- What is the final value stored at address
0x200 after the program finishes?
Answer
- Start PC = 0x100. Fetch `LOAD #5`, PC becomes 0x101. Decode "LOAD immediate". Execute: Accumulator = 5.
- Start PC = 0x101. Fetch `ADD #3`, PC becomes 0x102. Decode "ADD immediate". Execute: Accumulator = 5 + 3 = 8.
- Start PC = 0x102. Fetch `STORE 0x200`, PC becomes 0x103. Decode "STORE to memory". Execute: Memory at 0x200 gets value 8.
- Final value at 0x200 is **8**.
Exercise 3: Abstraction Layer Matching
Instructions: Draw a pyramid with 5 levels (or list them from bottom to top). Place these terms in the correct order from Lowest Level (most hardware) to Highest Level (most human):
Python Code, Binary Machine Code, Algorithm (Pseudocode), Digital Logic (Gates), Assembly Language.
Next to each level, write a one-sentence explanation of what that layer does.
Answer (Bottom to Top)
1. Digital Logic (Gates): Physical transistors implementing boolean operations.
2. Binary Machine Code: The raw 0s and 1s executed by the hardware.
3. Assembly Language: A human-readable mnemonic translation of machine code.
4. Python Code (High-Level Language): Abstract, readable instructions that are interpreted.
5. Algorithm (Pseudocode): A logical, language-independent plan for solving a problem.
Exercise 4: Memory Address Calculation
Instructions: You have a system with 32-bit memory addresses. This means memory addresses are 4 bytes long (since 32 bits = 4 bytes).
- If the Program Counter holds the value
0x1000 (hexadecimal), and each instruction takes up exactly 4 bytes.
- What address will the PC hold after three instructions have been fetched and the PC has incremented each time? Explain your calculation.
Answer
Start: 0x1000.
After fetching instruction 1: PC = 0x1000 + 4 = 0x1004.
After instruction 2: PC = 0x1004 + 4 = 0x1008.
After instruction 3: PC = 0x1008 + 4 = 0x100C.
Final PC = **0x100C**.
3.10 Homework Questions
Answer the following questions in complete sentences. Each response should be 3–5 sentences unless otherwise specified.
Short Answer Questions
1. Why did John von Neumann propose storing programs in the same memory as data? What practical problem did this solve compared to earlier machines like ENIAC?
Sample Answer
Von Neumann proposed the stored-program concept to eliminate the need for physical rewiring when changing tasks. Unlike ENIAC, which required hours or days of manual plugboard changes, a stored-program machine could load a new program from a drive or memory instantly. This made computers truly general-purpose and flexible.
2. Explain the "von Neumann bottleneck" and its practical effect on a running application.
Sample Answer
The von Neumann bottleneck occurs because the single shared bus between memory and the CPU can only carry either an instruction or a data value at a time. This means the CPU, which can execute instructions much faster, often spends idle time waiting for memory transfers. In practice, this limits how fast a program can run, especially in data-intensive applications.
3. What is the difference between a bit, a byte, and a word in a modern 64-bit computer?
Sample Answer
A bit is a single binary digit (0 or 1). A byte is a group of 8 bits, which is the fundamental addressing unit in almost all modern computers. A word is the natural processing size of the CPU; in a 64-bit computer, a word is 64 bits (8 bytes), meaning the CPU can process 8 bytes of data in a single operation.
4. Why is the hexadecimal system widely used in programming, particularly for memory dumps or debugging?
Sample Answer
Hex is used because it provides a compact and human-readable shorthand for binary data. Since each hex digit maps directly to 4 bits (a nibble), a memory address or byte value can be represented with only two hex digits instead of eight binary digits, drastically reducing the chance of human error when reading low-level data.
5. In the fetch-decode-execute cycle, what role does the Instruction Register (IR) play, and why is it distinct from the Program Counter (PC)?
Sample Answer
The Program Counter (PC) holds the address of the *next* instruction to fetch, acting as a pointer. The Instruction Register (IR) holds the actual binary pattern of the *currently executing* instruction after it has been fetched. They are distinct because the PC manages flow control (where we are going), while the IR contains the active command (what we are doing right now).
Essay Questions
Answer the following questions in 300–500 words each.
6. Trace the journey of a high-level Python statement, result = a + b, from the user's keystrokes to the final result appearing in memory. Explicitly discuss how the levels of abstraction (digital logic, machine code, assembly, high-level language) and the fetch-decode-execute cycle are involved.
Suggested outline:
- Introduction: The statement is simple for the programmer but complex for hardware.
- Step 1: Python interpreter parses the code and generates bytecode.
- Step 2: The Python Virtual Machine (PVM) translates bytecode to machine code for the specific CPU (x86/ARM).
- Step 3: The OS loads the machine code into RAM. The PC points to the first instruction.
- Step 4: FDE Cycle: Fetch the "Load a into register" instruction, Decode it, Execute it.
- Step 5: FDE Cycle: Fetch the "Add b to register" instruction, Decode, Execute (ALU adds them).
- Step 6: FDE Cycle: Fetch the "Store result to memory address of 'result'" instruction.
- Step 7: Conclusion: This sequence happens in nanoseconds, demonstrating the power of abstraction.
7. Compare and contrast the von Neumann architecture with the Harvard architecture. In what specific scenarios would you prefer a Harvard architecture over a von Neumann one, and why?
Suggested outline:
- Introduction: Define both architectures clearly (shared bus vs. separate buses).
- Similarities: Both use the stored-program concept, CPU, ALU, CU, and memory.
- Differences: Memory layout (single vs. dual), bandwidth (sequential vs. simultaneous fetch).
- Advantages of von Neumann: Simpler design, cheaper to implement, flexible memory allocation (can allocate more memory to data if less code).
- Advantages of Harvard: Faster throughput (no instruction/data bus contention), inherent security (code memory can be set to read-only).
- Use Cases for Harvard: Embedded systems (e.g., Arduinos, DSPs for audio processing) where speed and determinism are critical, and program size is fixed. Use Cases for von Neumann: General-purpose computing (PCs, smartphones) where flexibility and ability to run arbitrary software outweigh the speed penalty.
Research Questions
These questions require additional research beyond the tutorial content.
8. Research the concept of "Endianness" (Big-Endian vs. Little-Endian). How does the von Neumann architecture's treatment of instructions and data as bytes relate to the storage of multi-byte numbers? Why does the x86 CPU family use little-endian, while some network protocols require big-endian?
9. Research the history of the EDVAC computer. Who were the key contributors besides John von Neumann? Why is the report attributed to von Neumann, and what controversies surround this attribution?
10. Modern CPUs implement a feature called "Speculative Execution" which relies on pipelining and branch prediction. Research what speculative execution is and explain how it depends on the fetch-decode-execute cycle. How did the Spectre and Meltdown vulnerabilities exploit this principle to read protected memory?
Previous | Tutorial index | Next